【Unity UGUI】各种优化效果
发表于2017-05-20
UGUI在项目开发中的使用就不多做介绍了,本篇文章给大家介绍的是UGUI各种优化效果。
所实现的UGUI效果需求如下:
- 支持动态缩放循环加载
- 支持不用Mask遮罩无限循环加载
- 支持ObjectPool动态加载
- 支持无限不规则子物体动态加载
- 支持拖动并点击和拖拽
- 支持拖动并拖拽
- 支持ScrollRect拖动自动吸附功能(拖动是否超过一半自动进退)
前言
要实现以上效果,我从网上搜索得到部分解决方案链接,但不是完全满足想要的效果,就自己继续改造优化和添加想要的效果,本文最后会附带上完整Demo下载链接。
效果图
- 缩放循环展示卡牌效果
- 大量数据无卡顿动态加载,并且支持拖拽、点击和吸附功能
- 无限无遮罩动态加载
- 不规则子物体动态循环加载
部分核心代码
- 有遮罩无卡顿加载
思路:并没有使用UGUI的ScrollRect组件,摆放几张卡片,通过移动和缩放来实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 | using UnityEngine; using System.Collections; using System.Collections.Generic; using UnityEngine.UI; public class EnhancelScrollView : MonoBehaviour { // 缩放曲线 public AnimationCurve scaleCurve; // 位移曲线 public AnimationCurve positionCurve; // 位移系数 public float posCurveFactor = 500.0f; // y轴坐标固定值(所有的item的y坐标一致) public float yPositionValue = 46.0f; // 添加到EnhanceScrollView的目标对象 public List scrollViewItems; // 目标对象Widget脚本,用于depth排序 private List // 当前处于中间的item private EnhanceItem centerItem; private EnhanceItem preCenterItem; // 当前出移动中,不能进行点击切换 private bool canChangeItem = true ; // 计算差值系数 public float dFactor = 0.2f; // 点击目标移动的横向目标值 private float [] moveHorizontalValues; // 对象之间的差值数组(根据差值系数算出) private float [] dHorizontalValues; // 横向变量值 public float horizontalValue = 0.0f; // 目标值 public float horizontalTargetValue = 0.1f; // 移动动画参数 private float originHorizontalValue = 0.1f; public float duration = 0.2f; private float currentDuration = 0.0f; private static EnhancelScrollView instance; public static EnhancelScrollView GetInstance() { return instance; } void Awake() { instance = this ; } void Start() { if ((scrollViewItems.Count % 2) == 0) { Debug.LogError( "item count is invaild,please set odd count! just support odd count." ); } if (moveHorizontalValues == null ) moveHorizontalValues = new float [scrollViewItems.Count]; if (dHorizontalValues == null ) dHorizontalValues = new float [scrollViewItems.Count]; if (imageTargets == null ) imageTargets = new List int centerIndex = scrollViewItems.Count / 2; for ( int i = 0; i < scrollViewItems.Count;i++ ) { scrollViewItems[i].scrollViewItemIndex = i; Image tempImage = scrollViewItems[i].gameObject.GetComponent imageTargets.Add(tempImage); dHorizontalValues[i] = dFactor * (centerIndex - i); dHorizontalValues[centerIndex] = 0.0f; moveHorizontalValues[i] = 0.5f - dHorizontalValues[i]; scrollViewItems[i].SetSelectColor( false ); } centerItem = scrollViewItems[centerIndex]; canChangeItem = true ; } public void UpdateEnhanceScrollView( float fValue) { for ( int i = 0; i < scrollViewItems.Count; i++) { EnhanceItem itemScript = scrollViewItems[i]; float xValue = GetXPosValue(fValue, dHorizontalValues[itemScript.scrollViewItemIndex]); float scaleValue = GetScaleValue(fValue, dHorizontalValues[itemScript.scrollViewItemIndex]); itemScript.UpdateScrollViewItems(xValue, yPositionValue, scaleValue); } } void Update() { currentDuration += Time.deltaTime; if (currentDuration > duration) { // 更新完毕设置选中item的对象即可 currentDuration = duration; if (centerItem != null ) centerItem.SetSelectColor( true ); if (preCenterItem != null ) preCenterItem.SetSelectColor( false ); canChangeItem = true ; } SortDepth(); float percent = currentDuration / duration; horizontalValue = Mathf.Lerp(originHorizontalValue, horizontalTargetValue, percent); UpdateEnhanceScrollView(horizontalValue); } ///
/// 缩放曲线模拟当前缩放值 /// private float GetScaleValue( float sliderValue, float added) { float scaleValue = scaleCurve.Evaluate(sliderValue + added); return scaleValue; } ///
/// 位置曲线模拟当前x轴位置 /// private float GetXPosValue( float sliderValue, float added) { float evaluateValue = positionCurve.Evaluate(sliderValue + added) * posCurveFactor; return evaluateValue; } public void SortDepth() { imageTargets.Sort( new CompareDepthMethod()); for ( int i = 0; i < imageTargets.Count; i++) imageTargets[i].transform.SetSiblingIndex(i); } ///
/// 用于层级对比接口 /// public class CompareDepthMethod : IComparer { public int Compare(Image left, Image right) { if (left.transform.localScale.x > right.transform.localScale.x) return 1; else if (left.transform.localScale.x < right.transform.localScale.x) return -1; else return 0; } } ///
/// 获得当前要移动到中心的Item需要移动的factor间隔数 /// private int GetMoveCurveFactorCount( float targetXPos) { int centerIndex = scrollViewItems.Count / 2; for ( int i = 0; i < scrollViewItems.Count;i++ ) { float factor = (0.5f - dFactor * (centerIndex - i)); float tempPosX = positionCurve.Evaluate(factor) * posCurveFactor; if (Mathf.Abs(targetXPos - tempPosX) < 0.01f) return Mathf.Abs(i - centerIndex); } return -1; } ///
/// 设置横向轴参数,根据缩放曲线和位移曲线更新缩放和位置 /// public void SetHorizontalTargetItemIndex( int itemIndex) { if (!canChangeItem) return ; EnhanceItem item = scrollViewItems[itemIndex]; if (centerItem == item) return ; canChangeItem = false ; preCenterItem = centerItem; centerItem = item; // 判断点击的是左侧还是右侧计算ScrollView中心需要移动的value float centerXValue = positionCurve.Evaluate(0.5f) * posCurveFactor; bool isRight = false ; if (item.transform.localPosition.x > centerXValue) isRight = true ; // 差值,计算横向值 int moveIndexCount = GetMoveCurveFactorCount(item.transform.localPosition.x); if (moveIndexCount == -1) { moveIndexCount = 1; } float dvalue = 0.0f; if (isRight) dvalue = -dFactor * moveIndexCount; else dvalue = dFactor * moveIndexCount; // 更改target数值,平滑移动 horizontalTargetValue += dvalue; currentDuration = 0.0f; originHorizontalValue = horizontalValue; } ///
/// 向右选择角色按钮 /// public void OnBtnRightClick() { if (!canChangeItem) return ; int targetIndex = centerItem.scrollViewItemIndex + 1; if (targetIndex > scrollViewItems.Count - 1) targetIndex = 0; SetHorizontalTargetItemIndex(targetIndex); } ///
/// 向左选择按钮 /// public void OnBtnLeftClick() { if (!canChangeItem) return ; int targetIndex = centerItem.scrollViewItemIndex - 1; if (targetIndex < 0) targetIndex = scrollViewItems.Count - 1; SetHorizontalTargetItemIndex(targetIndex); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | using UnityEngine; using System.Collections; using UnityEngine.UI; public class EnhanceItem : MonoBehaviour { // 在ScrollViewitem中的索引 // 定位当前的位置和缩放 public int scrollViewItemIndex = 0; public bool inRightArea = false ; private Vector3 targetPos = Vector3.one; private Vector3 targetScale = Vector3.one; private Transform mTrs; private Image mImage; void Awake() { mTrs = this .transform; mImage = this .GetComponent } void Start() { this .gameObject.GetComponent ().onClick.AddListener( delegate () { OnClickScrollViewItem(); }); } // 当点击Item,将该item移动到中间位置 private void OnClickScrollViewItem() { EnhancelScrollView.GetInstance().SetHorizontalTargetItemIndex(scrollViewItemIndex); } ///
/// 更新该Item的缩放和位移 /// public void UpdateScrollViewItems( float xValue, float yValue, float scaleValue) { targetPos.x = xValue; targetPos.y = yValue; targetScale.x = targetScale.y = scaleValue; mTrs.localPosition = targetPos; mTrs.localScale = targetScale; } public void SetSelectColor( bool isCenter) { if (mImage == null ) mImage = this .GetComponent if (isCenter) mImage.color = Color.white; else mImage.color = Color.gray; } } |
- 有遮罩无卡顿加载
思路:协程加载,先加载屏幕显示的数量,然后返回一帧在继续加载,防止出现数量太大卡顿的现象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | while (CardsList.Count > roleInfo.Count) { DestroyImmediate(CardsList[0].gameObject); CardsList.RemoveAt(0); } StartCoroutine(createRoleCards()); private IEnumerator createRoleCards() { List charInfos = new List(); charInfos.AddRange(roleInfo); int index = 0; for ( int i = 0; i < charInfos.Count; i++) { _createRoleCard(charInfos[i], index++); if (index % 10 == 0) yield return null ; } } private void _createRoleCard(CLocalCharInfo roleInfo, int index) { CUIPlayedCharCardWidget charCardWidget = null ; if (CardsList.Count > index) { charCardWidget = CardsList[index]; } else { var obj = Instantiate(Resources.Load( "Prefab/RoleCard" )) as GameObject; if (obj == null ) { UnityEngine.Debug.LogError( "有误" ); return ; } obj.name = roleInfo.Name; charCardWidget = obj.GetComponent(); if (charCardWidget == null ) { UnityEngine.Debug.LogError( "有误" ); return ; } obj.transform.parent = Obj_ScrollViewContent.transform; obj.transform.localScale = Vector3.one; CardsList.Add(charCardWidget); } CUIPlayedCharCardWidget.CUIContent uiContent = new CUIPlayedCharCardWidget.CUIContent(); uiContent.RoleInfo = roleInfo; uiContent.ScrollRectObj = m_ScrollRect; uiContent.FixGridRect = m_FixGrid; charCardWidget.InitContent(uiContent); } |
- 支持ScrollRect拖拽或点击
思路:在卡片的Image上添加一个继承了IBeginDragHandler,IGradHandler,IEndDragHandler的脚本,重写接口里面的Drag事件方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; namespace Mga { [RequireComponent( typeof (Image))] public class CPlayedCardOnDrag : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler { public bool dragOnSurfaces = true ; public ScrollRect m_ScrollRect = null ; public CFixGridRect m_FixGridRect = null ; private GameObject m_DraggingCard; private RectTransform m_DraggingPlane; public bool isVertical = false ; private bool isSelf = false ; private System.Action m_OnBeginDragCallBack = null ; private System.Action m_OnEndDragCallBack = null ; private System.Action m_OnBeginScroll = null ; private System.Action m_OnEndScroll = null ; public void Init(CLocalCharInfo roleInfo, System.Action beginCallBack = null , System.Action endCallBack = null , System.Action beginScroll = null , System.Action endScroll = null ) { m_OnBeginDragCallBack = beginCallBack; m_OnEndDragCallBack = endCallBack; m_OnBeginScroll = beginScroll; m_OnEndScroll = endScroll; } public void OnBeginDrag(PointerEventData eventData) { Vector2 touchDeltaPosition = Vector2.zero; #if UNITY_EDITOR float delta_x = Input.GetAxis( "Mouse X" ); float delta_y = Input.GetAxis( "Mouse Y" ); touchDeltaPosition = new Vector2(delta_x, delta_y); #elif UNITY_ANDROID || UNITY_IPHONE touchDeltaPosition = Input.GetTouch(0).deltaPosition; #endif if (isVertical) { if (Mathf.Abs(touchDeltaPosition.x) > Mathf.Abs(touchDeltaPosition.y)) { isSelf = true ; var canvas = FindInParents(gameObject); if (canvas == null ) return ; m_DraggingCard = createCard(); m_DraggingCard.transform.SetAsLastSibling(); m_DraggingCard.AddComponent(); if (dragOnSurfaces) m_DraggingPlane = transform as RectTransform; else m_DraggingPlane = canvas.transform as RectTransform; SetDraggedPosition(eventData); if (m_OnBeginDragCallBack != null ) { m_OnBeginDragCallBack(); } } else { isSelf = false ; if (m_ScrollRect != null ) m_ScrollRect.OnBeginDrag(eventData); } } else { if (Mathf.Abs(touchDeltaPosition.x) < Mathf.Abs(touchDeltaPosition.y)) { isSelf = true ; var canvas = FindInParents(gameObject); if (canvas == null ) return ; m_DraggingCard = createCard(); m_DraggingCard.transform.SetAsLastSibling(); m_DraggingCard.AddComponent(); if (dragOnSurfaces) m_DraggingPlane = transform as RectTransform; else m_DraggingPlane = canvas.transform as RectTransform; SetDraggedPosition(eventData); if (m_OnBeginDragCallBack != null ) { m_OnBeginDragCallBack(); } } else { isSelf = false ; if (m_ScrollRect != null ) m_ScrollRect.OnBeginDrag(eventData); } } if (m_OnBeginScroll != null ) m_OnBeginScroll(); } public void OnDrag(PointerEventData data) { if (isSelf) { if (m_DraggingCard != null ) { SetDraggedPosition(data); } } else { if (m_ScrollRect != null ) m_ScrollRect.OnDrag(data); } } private void SetDraggedPosition(PointerEventData data) { if (dragOnSurfaces && data.pointerEnter != null && data.pointerEnter.transform as RectTransform != null ) m_DraggingPlane = data.pointerEnter.transform as RectTransform; var rt = m_DraggingCard.GetComponent(); Vector3 globalMousePos; if (RectTransformUtility.ScreenPointToWorldPointInRectangle(m_DraggingPlane, data.position, data.pressEventCamera, out globalMousePos)) { rt.position = globalMousePos; rt.rotation = m_DraggingPlane.rotation; } } private GameObject createCard() { CUIPlayedCharCardWidget charCardWidget = null ; m_DraggingCard = Instantiate(Resources.Load( "Prefab/RoleCard" ) as GameObject); if (m_DraggingCard == null ) { return null ; } charCardWidget = m_DraggingCard.GetComponent(); if (charCardWidget == null ) { return null ; } CUIPlayedCharCardWidget.CUIContent uiContent = new CUIPlayedCharCardWidget.CUIContent(); charCardWidget.InitContent(uiContent); return m_DraggingCard; } public void OnEndDrag(PointerEventData eventData) { if (isSelf) { if (m_DraggingCard != null ) { Destroy(m_DraggingCard); if (m_OnEndDragCallBack != null ) { m_OnEndDragCallBack(); } } } else { if (m_ScrollRect != null ) m_ScrollRect.OnEndDrag(eventData); if (m_FixGridRect != null ) m_FixGridRect.OnEndDrag(eventData); } } static public T FindInParents(GameObject go) where T : Component { if (go == null ) return null ; var comp = go.GetComponent(); if (comp != null ) return comp; Transform t = go.transform.parent; while (t != null && comp == null ) { comp = t.gameObject.GetComponent(); t = t.parent; } return comp; } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | using System.Reflection; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; using System.Collections; namespace Mga { public class CPlayedCardOnDrop : MonoBehaviour, IDropHandler, IPointerEnterHandler, IPointerExitHandler { public Image containerImage; public Image receivingImage; private Color normalColor; public Color highlightColor = Color.yellow; private int drapAreaIndex = 0; void Start() { drapAreaIndex = System.Convert.ToInt32( this .transform.parent.name); } public void OnEnable() { if (containerImage != null ) normalColor = containerImage.color; } public void OnDrop(PointerEventData data) { containerImage.color = normalColor; if (receivingImage == null ) return ; Sprite dropSprite = GetDropSprite(data); if (dropSprite != null ) receivingImage.overrideSprite = dropSprite; } public void OnPointerEnter(PointerEventData data) { if (containerImage == null ) return ; Sprite dropSprite = GetDropSprite(data); if (dropSprite != null ) containerImage.color = highlightColor; } public void OnPointerExit(PointerEventData data) { if (containerImage == null ) return ; containerImage.color = normalColor; } private Sprite GetDropSprite(PointerEventData data) { var originalObj = data.pointerDrag; if (originalObj == null ) return null ; var srcImage = originalObj.GetComponent if (srcImage == null ) return null ; return srcImage.sprite; } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | using System.Collections.Generic; using DG.Tweening; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; //TODO:当前只试应横向的ScrollRect,还需要扩展支持纵向 public class CFixGridRect : MonoBehaviour, IEndDragHandler { public GameObject content; public ScrollRect scorllRect; public float itemWidth; private RectTransform contentRectTf; private float formalPosX = 0; private float currentPosX = 0; private float halfItemLength = 0; void Start() { if (itemWidth <= 0) UnityEngine.Debug.LogError( "请设置Item的宽度" ); halfItemLength = itemWidth / 2; this .contentRectTf = this .content.GetComponent(); } public void OnEndDrag(PointerEventData eventData) { this .scorllRect.StopMovement(); Vector2 afterDragPagePos = this .content.transform.localPosition; currentPosX = afterDragPagePos.x; //当前拖动的位置 负 if (scorllRect.horizontalNormalizedPosition < 0 || scorllRect.horizontalNormalizedPosition > 1) return ; int count = ( int )(Mathf.Abs(currentPosX) / itemWidth); var targetPos = -( float )(count * itemWidth); if ((( float )(count * itemWidth + halfItemLength)) < Mathf.Abs(currentPosX)) { targetPos = -( float )((count + 1) * itemWidth); } formalPosX = targetPos; this .contentRectTf.DOLocalMoveX(targetPos, .2f); } } |
优化支持横竖屏的ScrollRect吸附功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | using System.Collections.Generic; using DG.Tweening; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; namespace Mga { public enum DragDirection { Horizontal, Vertical, } public class CFixGridRectBase : MonoBehaviour, IEndDragHandler { public class CUIContent { public GameObject ScrollRectContent; public ScrollRect m_ScorllRect; public float ItemSize; public float ItemSpaceLength; //间隙 public float Margin = 0; //顶部边缘间隙 public DragDirection m_DragDirection = DragDirection.Vertical; } private RectTransform contentRectTf; private float halfItemLength = 0; private CUIContent m_uiContent = null ; private bool m_bWidgetReady = false ; void Start() { m_bWidgetReady = true ; _initContent(); } public void InitContent(CUIContent uiContent) { m_uiContent = uiContent; if (m_bWidgetReady) _initContent(); } private void _initContent() { if (m_uiContent == null ) return ; if (m_uiContent.ItemSize <= 0) { UnityEngine.Debug.LogError( "请设置Item的宽度" ); return ; } halfItemLength = m_uiContent.ItemSize / 2; this .contentRectTf = m_uiContent.ScrollRectContent.GetComponent(); } public void OnEndDrag(PointerEventData eventData) { m_uiContent.m_ScorllRect.StopMovement(); Vector2 afterDragPagePos = m_uiContent.ScrollRectContent.transform.localPosition; var itemLength = m_uiContent.ItemSize + m_uiContent.ItemSpaceLength; if (m_uiContent.m_DragDirection == DragDirection.Horizontal) { var currentPosX = afterDragPagePos.x; //当前拖动的位置 负 currentPosX -= m_uiContent.Margin; int count = ( int )(Mathf.Abs(currentPosX) / m_uiContent.ItemSize); if (m_uiContent.m_ScorllRect.horizontalNormalizedPosition <= 0) { return ; } else if (m_uiContent.m_ScorllRect.horizontalNormalizedPosition >= 1) //总数-当前显示的数量 { return ; } var targetPosX = -( float )(count * itemLength); if ((( float )(targetPosX + halfItemLength)) < Mathf.Abs(currentPosX)) { count++; targetPosX = -( float )(count * itemLength); } this .contentRectTf.DOLocalMoveX(targetPosX, .2f); } else { var currentPosY = afterDragPagePos.y; //当前拖动的位置 正 currentPosY -= m_uiContent.Margin; int count = ( int )(Mathf.Abs(currentPosY) / itemLength); if (m_uiContent.m_ScorllRect.verticalNormalizedPosition <= 0) { return ; } else if (m_uiContent.m_ScorllRect.verticalNormalizedPosition >= 1) //总数-当前显示的数量 { return ; } var targetPosY = ( float )(count * itemLength); if ((( float )(targetPosY + halfItemLength)) < Mathf.Abs(currentPosY)) { count++; targetPosY = ( float )(count * itemLength); } this .contentRectTf.DOLocalMoveY(targetPosY, .2f); } } } } |