【NGUI学习之二】NGUI所见即所得之UIAnchor & UIStretch

    本文转自http://dsqiu.iteye.com/blog/1964679,感谢DSQiu同学的分享。



 NGUI所见即所得之UIAnchor & UIStretch

       NGUI的Example/Scenes/Example1介绍的主要就是UIRoot,UIAnchor和UIStretch这三个脚本,当然UIRoot是每个UI都会有的(挂在根节点),之前D.S.Qiu也写过博客介绍过UIRoot(猛点查看)。一直都对NGUI把Unity的单位和像素的转换和统一都很有疑惑,因之手游项目要做UI的自适应,就觉得很有必要把UIAnchor和UIStretch深入的研究下。

        UIRoot在D.S.Qiu的第一篇NGUI的文章中介绍了(猛点查看),UIRoot其实就做了一件事情:根据Screen.height和UIRoot.activeHeight的比例来调整UIRoot的loaclScal,从而保证UIWidget(UISprite,UILabel)可以按照其本身的大小进行设置,而不用经过复杂的换算过程。

         UIAnchor和UIStretch处理上的细节很相似,都是先计算参照对象(这个参照对象由Insprector的Container指定,如果没有选择,就是Camera)的大小Rect,然后根据参数UIAnchor(Side,relativeOffset,pixelOffset),UIStretch(Style,relativeSize,initialSize,borderPadding)进行调整,最后设置对应的属性,只不过UIAnchor设置的是transform.position,UIStretch设置的是(width,height)或clipRange等。

 

UIAnchor

        先看下UIAnchor的Inspector界面,感觉很简单,不会像UIPanel那么复杂:

        Container:指定Anchor的参照点,如果没有选择,则以Camera的pixelRect的区域为参照面

        Side:Anchor的定位方式

        Relative Offset:相对于Camera,或Container的百分比偏移

        Pixel Offset:像素的偏移

C#代码   收藏代码
  1.        /// <summary>  
  2. /// Relative offset value, if any. For example "0.25" with 'side' set to Left, means 25% from the left side.  
  3. /// </summary>  
  4. public Vector2 relativeOffset = Vector2.zero;  
  5.   
  6. /// <summary>  
  7. /// Pixel offset value if any. For example "10" in x will move the widget 10 pixels to the right   
  8. /// while "-10" in x is 10 pixels to the left based on the pixel values set in UIRoot.  
  9. /// </summary>  
  10. public Vector2 pixelOffset = Vector2.zero;  

 Update函数

       UIAnchor的主要功能实现都在Update函数:

C#代码   收藏代码
  1. void Update ()  
  2.     {  
  3.         if (mAnim != null && mAnim.enabled && mAnim.isPlaying) return;  
  4.         bool useCamera = false;  
  5.         UIWidget wc = (container == null) ? null : container.GetComponent<UIWidget>();  
  6.         UIPanel pc = (container == null && wc == null) ? null : container.GetComponent<UIPanel>();  
  7.         if (wc != null)  
  8.         {  
  9.             Bounds b = wc.CalculateBounds(container.transform.parent);  
  10.   
  11.             mRect.x = b.min.x;  
  12.             mRect.y = b.min.y;  
  13.   
  14.             mRect.width = b.size.x;  
  15.             mRect.height = b.size.y;  
  16.         }  
  17.         else if (pc != null)  
  18.         {  
  19.             if (pc.clipping == UIDrawCall.Clipping.None)  
  20.             {  
  21.                 // Panel has no clipping -- just use the screen's dimensions  
  22.                 float ratio = (mRoot != null) ? (float)mRoot.activeHeight / Screen.height * 0.5f : 0.5f;  
  23.                 mRect.xMin = -Screen.width * ratio;  
  24.                 mRect.yMin = -Screen.height * ratio;  
  25.                 mRect.xMax = -mRect.xMin;  
  26.                 mRect.yMax = -mRect.yMin;  
  27.             }  
  28.             else  
  29.             {  
  30.                 // Panel has clipping -- use it as the mRect  
  31.                 Vector4 pos = pc.clipRange;  
  32.                 mRect.x = pos.x - (pos.z * 0.5f);  
  33.                 mRect.y = pos.y - (pos.w * 0.5f);  
  34.                 mRect.width = pos.z;  
  35.                 mRect.height = pos.w;  
  36.             }  
  37.         }  
  38.         else if (container != null)  
  39.         {  
  40.             Transform root = container.transform.parent;  
  41.             Bounds b = (root != null) ? NGUIMath.CalculateRelativeWidgetBounds(root, container.transform) :  
  42.                 NGUIMath.CalculateRelativeWidgetBounds(container.transform);  
  43.   
  44.             mRect.x = b.min.x;  
  45.             mRect.y = b.min.y;  
  46.   
  47.             mRect.width = b.size.x;  
  48.             mRect.height = b.size.y;  
  49.         }  
  50.         else if (uiCamera != null)  
  51.         {  
  52.             useCamera = true;  
  53.             mRect = uiCamera.pixelRect;  
  54.         }  
  55.         else return;  
  56.   
  57.         float cx = (mRect.xMin + mRect.xMax) * 0.5f;  
  58.         float cy = (mRect.yMin + mRect.yMax) * 0.5f;  
  59.         Vector3 v = new Vector3(cx, cy, 0f);  
  60.   
  61.         if (side != Side.Center)  
  62.         {  
  63.             if (side == Side.Right || side == Side.TopRight || side == Side.BottomRight) v.x = mRect.xMax;  
  64.             else if (side == Side.Top || side == Side.Center || side == Side.Bottom) v.x = cx;  
  65.             else v.x = mRect.xMin;  
  66.   
  67.             if (side == Side.Top || side == Side.TopRight || side == Side.TopLeft) v.y = mRect.yMax;  
  68.             else if (side == Side.Left || side == Side.Center || side == Side.Right) v.y = cy;  
  69.             else v.y = mRect.yMin;  
  70.         }  
  71.   
  72.         float width = mRect.width;  
  73.         float height = mRect.height;  
  74.   
  75.         v.x += pixelOffset.x + relativeOffset.x * width;  
  76.         v.y += pixelOffset.y + relativeOffset.y * height;  
  77.   
  78.         if (useCamera)  
  79.         {  
  80.             if (uiCamera.orthographic)  
  81.             {  
  82.                 v.x = Mathf.Round(v.x);  
  83.                 v.y = Mathf.Round(v.y);  
  84.   
  85.                 if (halfPixelOffset && mNeedsHalfPixelOffset)  
  86.                 {  
  87.                     v.x -= 0.5f;  
  88.                     v.y += 0.5f;  
  89.                 }  
  90.             }  
  91.   
  92.             v.z = uiCamera.WorldToScreenPoint(mTrans.position).z;  
  93.             v = uiCamera.ScreenToWorldPoint(v);  
  94.         }  
  95.         else  
  96.         {  
  97.             v.x = Mathf.Round(v.x);  
  98.             v.y = Mathf.Round(v.y);  
  99.   
  100.             if (pc != null)  
  101.             {  
  102.                 v = pc.cachedTransform.TransformPoint(v);  
  103.             }  
  104.             else if (container != null)  
  105.             {  
  106.                 Transform t = container.transform.parent;  
  107.                 if (t != null) v = t.TransformPoint(v);  
  108.             }  
  109.             v.z = mTrans.position.z;  
  110.         }  
  111.   
  112.         // Wrapped in an 'if' so the scene doesn't get marked as 'edited' every frame  
  113.         if (mTrans.position != v) mTrans.position = v;  
  114.         if (runOnlyOnce && Application.isPlaying) Destroy(this);  
  115.     }  

       Update的原理很简单,梳理归纳为4部分:

                 1)获取Anchor参照对象的大小Rect以及计算中心点Vector3 v;

                 2)根据Side类型调整v的x,y,z值;

                 3)将v转换成世界坐标

                 4)将v赋给mTrans.position。

        这里对1)再多说几句,主要是涉及参照对象的选取问题,用if - else if来筛选的次序是 UIWidget wc , UIPanel pc , GameObject container, Camera uiCamera,如果前者部位null这取前者的大小后面的就不予考虑。

C#代码   收藏代码
  1. UIWidget wc = (container == null) ? null : container.GetComponent<UIWidget>();  
  2. container == null && wc == null) ? null : container.GetComponent<UIPanel>();  

 

像素与Unity单位

        之前项目中使用的NGUI版本还是2.6.3,那个版本还没有pixelOffset,然后为了实现一个相对便宜就在挂载Anchor的对象下面挂载一个子对象,通过设置子对象的loaclPosition来设置相对偏移:

      这样用transform.find去查找某一个子对象的时候就会觉得很蛋疼,所以当看到pixelOffset的就觉得没有必要用offset这层节点了,这可以说是NGUI埋下隐形的坑(很多没有“爱参考”不思考的开发者,就喜欢照搬别人的东西),之前的项目就是这样的,看了一堆Offset,完全没有必要。然后果断测试就会有以下不同的情况。

      测试之前首先将上面Bottom/Offset的localPosition置0,并修改稿Bottom的UIAnchor的pixelOffset改为(0,40)

1)当参照对象是Camera时,即Container=null:

但当编辑器的分辨率等于某个值时,又回恢复正常情况:


 

2)把Bottom的父对象UIPanel拖给UIAnchor的Container:

这种情况是没有问题的:


 

       回过头看下Update函数中对pixelOffset的使用:

C#代码   收藏代码
  1. v.x += pixelOffset.x + relativeOffset.x * width;  
  2. v.y += pixelOffset.y + relativeOffset.y * height;  

        经过反复的思考,觉得一定是pixelOffset和子对象Offset的localPosition数值的参考系是不一样的,但最终都是通过mRect来处理的,所以把UIAnchor Rect mRect设置成public,查看mRect的值,上面三个情况对应mRect值分别如下:



      这说明,当mRect.y大于等于800的时候,使用UIAnchor的pixelOffset和使用子对象Offset的localPosition的表现是一致的。但为什么指定Container为UIPanel都不会出现异常情况,只有Camera会出现。再回到Update看下获取mRect的方法,指定Container时,mRect实际是UIPanel,或UIWideget的像素大小,其实是UIWidget的(width,height),而没有指定Container情况下,mRect = camera.pixelRect。

      在UIRoot文中,就说过camera.pixelRect其实就是Screen的(width,height),也就是说,camera.pixelRect是会根据显示器的分辨率动态改变的,而指定Container时,mRect是不会改变的(在介绍UIRoot文中有介绍)。

       在看下pixelOffset的使用(真的是最后一次了):

C#代码   收藏代码
  1. v.x += pixelOffset.x + relativeOffset.x * width;  
  2. v.y += pixelOffset.y + relativeOffset.y * height;  

       虽然pixelOffset的值一直没有变化,但是当mRect = camera.pixelCamera时,mRect是随着分辨率的变化而变化的,那样的话pixelOffset占的权重就会改变了,所以才会出现上面的偏移异常。

       

解决策略

       在UIAnchor中设置一个参数unitOffset来代替子对象Offset的功能:

C#代码   收藏代码
  1. public Vector2 unitOffset = Vector2.zero;  

       然后把设置的值在Update函数的最后加个 mTrans.localPosition += new Vector3(unitOffset.x,unitOffset.y,0);

C#代码   收藏代码
  1. void Update()  
  2.        {      
  3.                //省略前面的处理。。。  
  4.                // Wrapped in an 'if' so the scene doesn't get marked as 'edited' every frame  
  5.     if (mTrans.position != v) mTrans.position = v;  
  6.        mTrans.localPosition += new Vector3(unitOffset.x,unitOffset.y,0);  
  7.     if (runOnlyOnce && Application.isPlaying) Destroy(this);  
  8.        }  

        这样就可以轻松取得GameObject树中Offset一层,之前项目中就有这个一层,我看着就来火,终于干掉了哈……

 

        还有一个问题:为什么当camera.pixelRect.y等于800时,就会恢复正常,这个先看下UIRoot的设置(对UIRoot原理不了解请猛击):

        Manual Height设置为800,Scaling Style设置为FixedSize,可以知道UI的放缩参考高度就是 800,即实际UI布局高度就是800,这里有点难理解,总之就是当屏幕分辨率高度等于800时,pixelOffset和子对象Offset的localPostion参考点就一致了,实际效果就一样了。也可以这么解释:当mRect = camera.pixelRect 时,pixeloffset的值是相对于camera.pixelRect而言的,在屏幕的呈现是会对着屏蔽的分辨率不同而改变的;而使用子对象Offset的localPosition的参照和UI组件是一致的,所以相对于Contaner的位置是不会改变的。

        回到文中开头抛出的一个问题——Unity中transfrom的单位和像素的关系,上张图片,可以知道UI的高度实际高度是800,然后看下Anchor - Top 和Anchor - Bottom的transform的localPostion.y分别是400.5006和-399.4994(图片如下),发现两者区间刚好是800,这就说明NGUI的架构中像素坐标和Unity的单位是一致的,对等的,即一个Unity单位等于一个像素。

UIStretch

       看下NGUI Example1 的Anchor - Stretch的Inspector面板,发现UIStretch只比UIAnchor多了一个参数,不过从这张图UISprite的Dimension(红框标出)的长度始终都是800,不管屏幕如何改变大小,都是800个像素,填充的Tiled图片个数也是一样的,这也更加说明了上面提到的一个结论:NGUI中Unity的单位和像素是同一的。

 Update

       UIStretch的Update函数的前面部分跟UIAnchor的Update的前面部分原理是一样的都是获取mRect,所以只看后一部分的实现(理解 relativeSize 和 initialSize 的含义和作用):

C#代码   收藏代码
  1. void Update()  
  2. {  
  3.                         //.......省略上面的处理  
  4.             float rectWidth = mRect.width;  
  5.             float rectHeight = mRect.height;  
  6.   
  7.             if (adjustment != 1f && rectHeight > 1f)  
  8.             {  
  9.                 float scale = mRoot.activeHeight / rectHeight;  
  10.                 rectWidth *= scale;  
  11.                 rectHeight *= scale;  
  12.             }  
  13.   
  14.             Vector3 size = (mWidget != null) ? new Vector3(mWidget.width, mWidget.height) : mTrans.localScale;  
  15.   
  16.             if (style == Style.BasedOnHeight)  
  17.             {  
  18.                 size.x = relativeSize.x * rectHeight;  
  19.                 size.y = relativeSize.y * rectHeight;  
  20.             }  
  21.             else if (style == Style.FillKeepingRatio)  
  22.             {  
  23.                 // Contributed by Dylan Ryan  
  24.                 float screenRatio = rectWidth / rectHeight;  
  25.                 float imageRatio = initialSize.x / initialSize.y;  
  26.   
  27.                 if (imageRatio < screenRatio)  
  28.                 {  
  29.                     // Fit horizontally  
  30.                     float scale = rectWidth / initialSize.x;  
  31.                     size.x = rectWidth;  
  32.                     size.y = initialSize.y * scale;  
  33.                 }  
  34.                 else  
  35.                 {  
  36.                     // Fit vertically  
  37.                     float scale = rectHeight / initialSize.y;  
  38.                     size.x = initialSize.x * scale;  
  39.                     size.y = rectHeight;  
  40.                 }  
  41.             }  
  42.             else if (style == Style.FitInternalKeepingRatio)  
  43.             {  
  44.                 // Contributed by Dylan Ryan  
  45.                 float screenRatio = rectWidth / rectHeight;  
  46.                 float imageRatio = initialSize.x / initialSize.y;  
  47.   
  48.                 if (imageRatio > screenRatio)  
  49.                 {  
  50.                     // Fit horizontally  
  51.                     float scale = rectWidth / initialSize.x;  
  52.                     size.x = rectWidth;  
  53.                     size.y = initialSize.y * scale;  
  54.                 }  
  55.                 else  
  56.                 {  
  57.                     // Fit vertically  
  58.                     float scale = rectHeight / initialSize.y;  
  59.                     size.x = initialSize.x * scale;  
  60.                     size.y = rectHeight;  
  61.                 }  
  62.             }  
  63.             else  
  64.             {  
  65.                 if (style != Style.Vertical)  
  66.                     size.x = relativeSize.x * rectWidth;  
  67.   
  68.                 if (style != Style.Horizontal)  
  69.                     size.y = relativeSize.y * rectHeight;  
  70.             }  
  71.   
  72.             if (mSprite != null)  
  73.             {  
  74.                 float multiplier = (mSprite.atlas != null) ? mSprite.atlas.pixelSize : 1f;  
  75.                 size.x -= borderPadding.x * multiplier;  
  76.                 size.y -= borderPadding.y * multiplier;  
  77.   
  78.                 if (style != Style.Vertical)  
  79.                     mSprite.width = Mathf.RoundToInt(size.x);  
  80.   
  81.                 if (style != Style.Horizontal)  
  82.                     mSprite.height = Mathf.RoundToInt(size.y);  
  83.   
  84.                 size = Vector3.one;  
  85.             }  
  86.             else if (mWidget != null)  
  87.             {  
  88.                 if (style != Style.Vertical)  
  89.                     mWidget.width = Mathf.RoundToInt(size.x - borderPadding.x);  
  90.   
  91.                 if (style != Style.Horizontal)  
  92.                     mWidget.height = Mathf.RoundToInt(size.y - borderPadding.y);  
  93.   
  94.                 size = Vector3.one;  
  95.             }  
  96.             else if (mPanel != null)  
  97.             {  
  98.                 Vector4 cr = mPanel.clipRange;  
  99.   
  100.                 if (style != Style.Vertical)  
  101.                     cr.z = size.x - borderPadding.x;  
  102.                   
  103.                 if (style != Style.Horizontal)  
  104.                     cr.w = size.y - borderPadding.y;  
  105.                   
  106.                 mPanel.clipRange = cr;  
  107.                 size = Vector3.one;  
  108.             }  
  109.             else  
  110.             {  
  111.                 if (style != Style.Vertical)  
  112.                     size.x -= borderPadding.x;  
  113.                   
  114.                 if (style != Style.Horizontal)  
  115.                     size.y -= borderPadding.y;  
  116.             }  
  117.               
  118.             if (mTrans.localScale != size)  
  119.                 mTrans.localScale = size;  
  120.   
  121.             if (runOnlyOnce && Application.isPlaying) Destroy(this);  
  122. }  

 整体放缩

     分析了 UIStretch 的 Update ,可以知道当 UIStretch 挂载的GameObject,有UIWidget,UISprite,UIPanel 这个几个脚本是,是不会放缩这个GameObject的子GameObject的,如果要整体放缩的话,就得通过倒数第二行: 

C#代码   收藏代码
  1. mTrans.localScale = size;    

       如果 Style 选择为 Both ,并且没有指定Container 且GameObject 上没有挂载UIWidget,UISprite,UIPanel ,抽出主要的执行代码:

C#代码   收藏代码
  1. void Update()  
  2. {  
  3.                         //省略前面的代码  
  4.                        else if (uiCamera != null)  
  5.             {  
  6.                 mRect = uiCamera.pixelRect;  
  7.                 if (mRoot != null) adjustment = mRoot.pixelSizeAdjustment;  
  8.             }  
  9.             else return;  
  10.   
  11.             float rectWidth = mRect.width;  
  12.             float rectHeight = mRect.height;  
  13.   
  14.             if (adjustment != 1f && rectHeight > 1f)  
  15.             {  
  16.                 float scale = mRoot.activeHeight / rectHeight;  
  17.                 rectWidth *= scale;  
  18.                 rectHeight *= scale;  
  19.             }  
  20.   
  21.                         //省略 代码  
  22.                         else  
  23.             {  
  24.                 if (style != Style.Vertical)  
  25.                     size.x = relativeSize.x * rectWidth;  
  26.   
  27.                 if (style != Style.Horizontal)  
  28.                     size.y = relativeSize.y * rectHeight;  
  29.             }  
  30.                         //省略 代码  
  31.                         if (mTrans.localScale != size)  
  32.                 mTrans.localScale = size;  
  33. }  

       整理下: mTrans.localScale.x = relativeSize.x * rectWidth * mRoot.activeHeight / rectHeight

mTrans.localScale.y = relativeSize.y * rectHeight * mRoot.activeHeight / rectHeight = relativeSize.y * mRoot .activeHeight

       因为 UIRoot 是基于高度调整 localScale 的 来做放缩的,所以宽度的放缩UIRoot 已经做了,所以只需要将 relativeSize.y 设置为 1 / mRoot.activeHeight 使 mTrans.localScale.y = 1恒成立。UIRoot 会是高度始终满屏(UIRoot Style 为 FixedSize ),但宽度的放缩总是按照高度的放缩比例在放的,所以会出现宽度没有全部显示出来,或者左右两边有黑边。

      其实要想做到满屏(高度和宽度)缩放的效果,其实可以在UIRoot中增加一个 manualWidth 来调整 UIRoot 的localSize.x 的值。

      另外一种做法就是使用UIStretch ,我们只需要通过设置 relativeSize.x = 1 / manualWidth ;relativeSize.y = 1 / mRoot.activeHeight 就能满屏缩放了,哈哈,搞定了。等等,这样是满屏了,但是其他图片或文字会被拉伸变形,也就是说 UIStretch 只能做到某个单一的组件按比例缩放。

      总之,实际屏幕显示的宽度 = (Screen.Height / mRoot.acriveHeight * manualHeight > Screen.Width ) ?  Screen.Width :Screen.Height / mRoot.acriveHeight * manualHeight ,就是去两者中的更小的。所以要做宽度放缩,只要针对实际显示宽度 和 屏幕宽度(Screen.Width) 来调整 localScale.x 值就行了。 

 

                                                                                                                                                                                         增补于 2013/11/26 19:45

 

      

       

 

                     

 

 

小结:

       本来昨天晚上就想写的,但是由于心情不好,也还要一些要点没有相同,所以才拖到现在完成,今天上了半天班(虽然一直都是在NBA的,今天的詹姆斯太牛逼了,看到我心情都好了,18投14中,罚球11中10,39分),有点小不敬业,吃完中饭之后,就开始组织些了,一直写到现在(花了5个小时),截了好些图,这篇应该是D.S.Qiu这么多篇中写得最畅快最爽的一次,解决之前的疑问。

       和UIRoot一样,UIAnchor和UIStretch很简单,但是却很重要,虽然就几个参数,但是要完全明白和理解还是需要花点时间的,所以我才写出来跟大家分享的,NGUI的文章页写了几篇了(点击查看),转载注明出处,尊重原创。

 

        如果您对D.S.Qiu有任何建议或意见可以在文章后面评论,或者发邮件(gd.s.qiu@gmail.com)交流,您的鼓励和支持是我前进的动力,希望能有更多更好的分享。

        转载请在文首注明出处:http://dsqiu.iteye.com/blog/1975972

更多精彩请关注D.S.Qiu的博客和微博(ID:静水逐风) 


Unity 3D NGUI实战教程 完整版 第1章 初识NGUI 1.1 游戏UI开发介绍 1.1.1 什么是游戏UI 1.1.2 UI为何如此重要 1.1.3 UI开发的流程 1.1.4 UI开发的难点 1.2 什么是NGUI 1.2.1 NGUI插件介绍 1.2.2 NGUI的强大优势 第2章 NGUI基础 2.1 导入NGUI插件 2.1.1 NGUI版本介绍 2.1.2 NGUI的下载和购买 2.1.3 导入NGUI插件应用 2.1.4 导入常见问题 2.2 认识基本的UI资源 2.2.1 什么是UI精灵(Sprite) 2.2.2 什么是UI图集(Atlas) 2.2.3 什么是UI贴图(Texture) 2.2.4 什么是UI标签(Label) 2.2.5 什么是UI字体(Font) 2.3 制作第一个UI图集 2.3.1 学会解剖UI的资源结构 2.3.2 如何导入切好的美术资源 2.3.3 用Atlas Maker制作图集 2.4 制作第一个UI字体 2.4.1 为什么要制作UI字体 2.4.2 静态字体和动态字体 2.4.3 制作静态字体介绍 2.4.4 制作动态字体介绍 2.5 创建第一个UI 2.5.1 创建一个2D UI 2.5 .2 创建一个3D UI 2.5.3 了解UIRootUIPanel和UICamera组件 2.6 2DUI和3DUI的工作原理 2.6.1 2DUI的工作原理 2.6.2 3DUI的工作原理 2.6.3 如何判断该选择哪一种UI 2.7 深度(Depth)概念 2.7.1 强化对深度的理解 2.7.2 小心相机的深度 第3章 核心组件 3.1 什么是UI控件 3.2 制作精灵(UISprite) 3.2.1 怎样判断是否应该使用精灵 3.2.2 创建精灵 3.2.3 Sprite组件的设置 3.3 制作标签(Label) 3.3.1 怎样判断是否应当使用标签 3.3.2 创建标签 3.3.3 Label的文字设置 3.4 制作UI纹理(UITexture) 3.4.1 什么情况下使用UITexture 3.4.2 创建纹理 3.4.3 纹理的设置 3.5 制作按钮(Button) 3.5.1 怎样判断应该使用按钮 3.5.2 创建按钮 3.5.3 核心组件BoxCollider 3.5.4 核心组件UIButton 3.5.5 制作按钮的放缩动画 3.5.6 制作按钮的偏移动画 3.5.7 制作按钮的旋转动画 3.5.8 添加按钮单击音效 3.5.9 任何事物都可以变成按钮,不仅仅是UI 3.6 制作进度条(UISlider) 3.6.1 怎样判断是否应当使用进度条 3.6.2 创建进度条 3.6.3 核心组件UISlider设置 3.6.4 进度条的BoxCollider说明 3.7 制作输入框(Input) 3.7.1 怎样判断是否应当使用输入框 3.7.2 创建输入框 3.7.3 核心组件Input设置 3.7.4 输入框使用的一些注意事项 3.8 制作滚动视图(ScrollView) 3.8.1 怎样判断是否应当使用滚动视图 3.8.2 创建滚动视图 3.8.3 滚动视图核心组件UIPanel 3.8.4 滚动视图核心组件UIScrollView 3.8.5 创建一个拖动条 3.8.6 拖动条说明 3.8.7 让视图内的内容可以被拖动 3.8.8 制作滚动视图时的注意事项 3.9 制作复选框(Toggle) 3.9.1 怎样判断是否应当使用复选框 3.9.2 创建复选框 3.9.3 复选框的核心组件UIToggle 3.10 制作下拉菜单(PopupList) 3.10.1 怎样判断是否应当使用下拉菜单 3.10.2 创建下拉菜单 3.10.3 显示当前选中的选项 3.10.4 下拉菜单核心组件PopupList 3.10.5 制作下拉菜单的注意事项 第4章 UI动画 4.1 常见的两种UI动画介绍 4.1.1 要区分UI动画和UI特效两个概念 4.1.2 关于Tween动画 4.1.3 关于Animation动画 4.2 渐隐渐现动画(透明度动画) 4.2.1 透明度动画的介绍和应用 4.2.2 使用透明度动画TweenAlpha 4.2.3 使用透明度动画的注意点 4.3 颜色变化动画(变色动画) 4.3.1 变色动画的介绍和应用 4.3.2 使用颜色动画TweenColor 4.3.3 使用颜色动画的注意点 4.4 位置变换动画(位移动画) 4.4.1 位移动画的介绍和应用 4.4.2 使用位移动画TweenPosition 4.4.3 使用位移动画的注意点 4.5 旋转变化动画(旋转动画) 4.5.1 旋转动画的介绍和应用 4.5.2 使用旋转动画TweenRotation 4.5.3 使用旋转动画的注意点 4.6 大小变化动画(放缩动画) 4.6.1 放缩动画的介绍和应用 4.6.2 使用放缩动画TweenScale 4.6.3 使用放缩动画的注意点 4.7 Tween动画总结 4.8 动画控制组件UIPlayTween 4.8.1 为什么要用UIPlayTween 4.8.2 动画核心组件UIPlayTween讲解 4.8.3 使用UIPlayTween的注意事项 4.9 动画控制组件UIPlayAnimation 4.9.1 为什么要用UIPlayAnimation 4.9.2 为UI添加Animation组件 4.9.3 动画核心组件UIPlayAnimation讲解 4.9.4 使用UIPlayAnimation注意事项 第5章 其他组件 5.1 使用Toggle制作页签 5.1.1 页签的工作原理 5.1.2 一个完整的页签界面 5.1.3 制作两个页签按钮 5.1.4 使用ToggleObjects来记录页签内容 5.1.5 制作页签注意事项 5.2 拖动摄像机来浏览超大界面 5.2.1 拖动相机功能的介绍和应用 5.2.2 核心原理和组件介绍 5.2.3 拖动相机浏览超大界面的注意事项 5.3 使用Grid自动排列UI 5.3.1 自动排列UI的应用 5.3.2 自动排列UI核心组件Grid介绍 5.4 使用DragObject直接拖动物体 5.5 让玩家通过拖动自由改变控件大小 5.6 制作序列帧精灵动画(SpriteAnimation) 5.6.1 什么是序列帧精灵动画 5.6.2 SpriteAnimation组件 第6章 NGUI实战进阶 6.1 UI开发核心问题--UI随屏幕自适应 6.1.1 屏幕分辨率对UI适配的影响 6.1.2 主流设备的屏幕分辨率 6.1.3 自适应核心组件Anchor的使用 6.1.4 使用Anchor的注意事项 6.1.5 正式开发UI之前必须明确的几个问题 6.2 UI元素的相对自适应 6.2.1 什么是UI元素的相对自适应 6.2.2 Anchors的介绍及使用 6.2.3 使用Anchors的范例:背景图的全屏适配 6.2.4 使用Anchors的注意事项 6.3 多摄像机同时协作运行 6.3.1 摄像的渲染层的概念 6.3.2 多摄像机协作的应用范围 6.3.3 如何创建多个UI摄像机 6.3.4 多摄像机协作的注意事项 6.4 巧用九宫格以减少UI资源量 6.4.1 项目安装包大小对项目的影响
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值