前言
今天要推薦的是 Unity 的 LoopScrollRect 插件,一款專為【無限滾動列表】與【高效內容加載】需求設計的強大工具,能夠幫助開發者輕鬆應對大數據列表顯示的挑戰!不僅支持靈活的【動態內容更新】、【節省內存】,還提供直觀的【滾動加載邏輯設計】,無論是【玩家排行榜】、【商品列表】,還是【社交動態流】,LoopScrollRect 都能高效解決你的開發需求。
本篇文章將帶你深入了解如何快速上手 LoopScrollRect 插件,包含【設置滾動列表】、【實現無縫數據加載】以及【優化內存性能】的技巧,整篇文章包括對插件的【詳細介紹】與【補充說明】,幫助開發者在大數據場景中流暢呈現內容,避免常見問題,提升用戶體驗!如果你正在尋找能實現流暢無縫滾動的利器,千萬不要錯過 LoopScrollRect 這個插件!
Unity LoopScrollRect 插件介紹
LoopScrollRect 是一款專為 Unity 設計的高效滾動內容管理插件,它通過【循環滾動】與【數據加載】的創新方式,大幅減少 UI 元素內存占用,實現大數據場景下的流暢滾動與無縫內容顯示,無論是【玩家排行榜】、【商品展示列表】,還是【社交訊息顯示】,LoopScrollRect 這個插件都能提供穩定且高效的解決方案,是 Unity 開發者不可或缺的 UI 工具。
功能特色
- 動態內容加載能力
支持【動態生成】與【釋放 UI 元素】,只渲染當前可見的內容,顯著降低內存佔用,適合有大量數據的場景。 - 靈活的滾動行為控制
支持【垂直】或【水平】滾動,並兼容各種 UI 布局,提供自定義【滾動速度】、【緩動效果】與【內容定位】功能。 - 高效性能優化
針對 Unity 進行性能調校,在保持流暢滾動的同時,確保 UI 元素高效【加載】與【釋放】,提升用戶體驗。
適用場景
- 玩家排行榜顯示
在多人遊戲中實現即時更新的排行榜,無需載入整個列表即可顯示數百甚至上千名玩家的數據。 - 商品展示列表
適合【遊戲商城】或【資源管理】等應用,支持無限滾動並顯示動態加載的商品信息,實現流暢的購物體驗。 - 動態訊息顯示
在遊戲大廳中實現消息滾動顯示功能,支持【即時內容更新】與【循環滾動】的設計,提升用戶的【互動體驗】與【資訊接收效率】。
Unity LoopScrollRect 外掛
插件可以通過 github 進行下載,鏈接:
Unity LoopScrollRect 插件作者文檔解讀
插件作者在忙於性能優化時,深切體會到二八法則真是指導高 (tou) 效 (lan) 工作的強大武器。在某個禮拜花了幾天解決了一個實際問題:UGUI 的 ScrollRect 加載太多物體的時候,第一次彈出界面會非常卡頓,而且不在界面裡的內容依然會參與繪製(毫無意義的浪費…) 。
變更日誌
- v1.03 終於支援了 ScrollBar,支援直接創建。
- v1.02 Bug Fixes,無盡模式。
- v1.01 重構了好幾遍,基本算重寫了份…優化了拖曳手感和回收部分的計算,增加了反方向滑動支援。升級至 Unity 5.2 的 UGUI API。
- v1.0 這兩天基於網上找的一份 InfinityScroll 代碼,把這個功能做了。在加載時間和 Draw Call 上都提升顯著,而且滑動的時候也沒有卡頓。
每個元素知道自己的序號,可以依需求修改自己的內容、大小等資訊。
此外支援了 ScrollBar,支援橫向、縱向及正反向。
在關閉 Mask 後可以看到,只有在需要的時候才動態實例化元素,使用完後再回收。
介紹
最原始版本的程式碼是 @ivomarel 的 InfinityScroll。插件作者改到後來,基本上和原文版沒啥相同的了…
- 原始碼使用了
sizeDelta
作為大小,但是這個在錨點不重合情況下是不成立的。 - 支持了 GridLayout。
- 啟動時檢查錨點和軸心,方便使用。
- 修正了原代碼在往前拖曳會卡頓的問題。
- 優化程式碼,提升效能。
- 支援反向滑動。
- 支援 ScrollBar (在無盡模式下不起作用; 如果元素大小不一致會出現捲軸瑕疵)。
另外,插件作者修改了 Easy Object Pool 作為池子,循環利用元素。
警告: 為了解決原始程式碼回拉卡頓的問題,插件作者直接複製了一份 UGUI 中的 ScrollRect 程式碼,而沒有繼承。這是因為老的做法是在 onDrag
裡面停止並立即啟動滾動,而插件作者透過修改兩個私有變數保證了滑動順暢。所有作者的程式碼都用 ==========LoopScrollRect==========
這樣的註解包起來,維護起來就像打 patch了…。
框架思路
和 UGUI 自帶的 ScrollRect
有所不同,插件作者拆分出了 LoopHorizontalScrollRect
和 LoopVerticalScrollRect
兩個類,分別代表水平滾動條和水平滾動條。下面插件作者以 LoopVerticalScrollRect
為例,水平版類似。
protected override float GetSize(RectTransform item)
{
float size = contentSpacing;
if (m_GridLayout != null)
{
size += m_GridLayout.cellSize.y;
}
else
{
size += LayoutUtility.GetPreferredHeight(item);
}
return size;
}
這個其實也是最核心的一個地方:在能夠準確計算格子大小的基礎上,後續工作就好實現了。
如何優雅的增刪元素
對於每個 ScrollRect
,其實只需要考慮在頭部和尾部是否需要增加或刪除元素。這裡以頭部的各種情況為例進行解釋,因為在正向滑動情況下,必須保證在修改元素之後整個 ScrollRect
內容顯示一致不跳變;這些情況比尾部處理會麻煩一些。
NewItemAtStart
函數實作了在頭部增加一個(或一行,針對 GridLayout
)元素,並傳回這些元素的高度;DeleteItemAtStart
代表刪除頭部的一個元素。需要注意的是,在修改頭部元素之後要及時修改 content
的 anchoredPosition
,這樣才能確保整個內容區域不會因為多了或少了一行而產生跳變。
protected float NewItemAtStart()
{
float size = 0;
for (int i = 0; i < contentConstraintCount; i++)
{
// Get Element from ObjectPool
}
if (!reverseDirection)
{
// Modify content.anchoredPosition
}
return size;
}
protected float DeleteItemAtStart()
{
float size = 0;
for (int i = 0; i < contentConstraintCount; i++)
{
// Return Element to ObjectPool
}
if (!reverseDirection)
{
// Modify content.anchoredPosition
}
return size;
}
何時需要增刪元素
這裡需要有兩個概念 viewBounds
和 contentBounds
:前者是指 ScrollRect
本身的大小,一般也對應 Mask;後者是指 ScrollRect
裡所有 cell 組成的內容部分的大小。在這個基礎上就簡單了:如果 contentBounds
的最上面比 viewBounds
的最上面要低,那麼嘗試在頂部增加元素;如果 contentBounds
的最上面比 viewBounds
的最上面高很多,那麼嘗試刪除元素。
protected override bool UpdateItems(Bounds viewBounds, Bounds contentBounds)
{
bool changed = false;
// cases for NewItemAtEnd/DeleteItemAtEnd
if (viewBounds.max.y > contentBounds.max.y - 1)
{
float size = NewItemAtStart();
if (size > 0)
{
changed = true;
}
}
else if (viewBounds.max.y < contentBounds.max.y - threshold)
{
float size = DeleteItemAtStart();
if (size > 0)
{
changed = true;
}
}
return changed;
}
物件池交互
在新建 cell 和銷毀 cell 的時候,使用物件池來避免記憶體碎片;同時這裡使用了 SendMessage
來向每個 cell 發送必須的訊息,保證資料的正確性。
private void SendMessageToNewObject(Transform go, int idx)
{
go.SendMessage("ScrollCellIndex", idx);
}
private void ReturnObjectAndSendMessage(Transform go)
{
go.SendMessage("ScrollCellReturn", SendMessageOptions.DontRequireReceiver);
prefabPool.ReturnObjectToPool(go.gameObject);
}
private RectTransform InstantiateNextItem(int itemIdx)
{
RectTransform nextItem = prefabPool.GetObjectFromPool(prefabPoolName).GetComponent<RectTransform>();
nextItem.transform.SetParent(content, false);
nextItem.gameObject.SetActive(true);
SendMessageToNewObject(nextItem, itemIdx);
return nextItem;
}
捲軸相關
這塊插件作者其實是估算的,根據當前的長度和當前元素個數/總個數按照比例縮放,這個在所有 cell 大小一致的情況下是沒有問題的;但是如果大小不一致我就無法得到精確結果,所以會產生一定抖動。插件作者暫時沒有更好辦法,因為得到的資訊就是不夠用…。
其他細節
插件作者主要遇到了兩個坑:
- 增加或刪除元素之後,有時候需要強制呼叫
Canvas.ForceUpdateCanvases()
刷新下。 - 注意不要在 Build Canvas 過程中再次修改元素,從而再次觸發 Build Canvas…。
使用範例
以垂直滾動條為例,介紹一下步驟。如果覺得麻煩的話,直接打開 DemoScene 複製貼上就好,當然也可以幹掉 EasyObjPool,自己控制生成和銷毀。
- 準備好 Prefabs
- 每個物體上需要貼上
Layout Element
並指定 preferred width/height。 - 貼上腳本接受
void ScrollCellIndex (int idx)
訊息,從而對每個位置的元素根據需要靈活修改。
- 每個物體上需要貼上
- 在 Hierarchy 裡右鍵,選擇 UI/Loop Horizontal Scroll Rect 或 UI/Loop Vertical Scroll Rect 即可。使用 Component 菜單裡的也是一樣的。
- Init in Start: 啟動時自動呼叫 Refill cells 初始化。
- Prefab Pool: EasyObjPool 物體。
- Prefab Pool Name: 第二步對應的 Cell Prefab 名字。
- Total Count: 總共能有多少物體,範圍 0 ~ TotalCount-1。
- Threshold: 兩端預留的快取量(像素數)。
- ReverseDirection: 如果是從下往上或從右往左拖動,就打開這裡。
- Clear Cells: 清除已有元素,恢復到未初始化狀態。
- Refill Cells: 初始化並填滿元素。
如果是正向滑動,就設定 pivot 為 1;否則設為 0 並開啟 ReverseDirection。插件作者強烈建議試試在播放狀態下試試看修改這些參數。
無盡模式
如果需要無限滾動模式,將 totalCount
設為負數即可。
他人工作
後來插件作者搜了下,發現網路上也有人提到 UGUI ScrollRect 優化,不過他的策略是監聽 ScrollRect 的 value,然後停用範圍外的 cell。最後 UGUI ScrollRect 優化的作者也提到改成動態載入策略。這種基於 value 的做法我不太確認在滾動前動態添加新元素的時候是否會出現問題。
Unity LoopScrollRect 導入插件
把這個連結複製到 Package Manager 中進行中進行導入:
https://github.com/qiankanglai/LoopScrollRect. git
Unity LoopScrollRect 查看案例
如果你導入插件成功,點擊 DemoScene 進入演示場景,找到多行多列,上下滾動的案例。
Unity LoopScrollRect 使用
可以在 Inspector 中看到他身上掛著一個腳本。
這個腳本就是用來初始化滾動容器的核心,我們新建立一個自己的腳本,把 InitOnStart 中的程式碼抄過來,然後改成自己的,我命名為:PackageScroll.cs。
腳本中的幾個要點:
- Start 函數中對 LoopScrollRect 進行初始化,唯一需要修改的是 ls.totalCount,改成自己的物品總量即可。
- ProvideData:實作 LoopScrollDataSource 接口,子物件從這裡拿到資料來刷新自身
- 其他可以保持案例中的程式碼不變,GetObject 和 ReturnObject 實作了一個類似物件池的快取功能。
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.UI;
namespace Demo
{
[RequireComponent(typeof(UnityEngine.UI.LoopScrollRect))]
[DisallowMultipleComponent]
public class PackageScroll : MonoBehaviour, LoopScrollPrefabSource, LoopScrollDataSource
{
public GameObject item;
public int totalCount = -1;
// Implement your own Cache Pool here. The following is just for example.
Stack<Transform> pool = new Stack<Transform>();
public GameObject GetObject(int index)
{
if (pool.Count == 0)
{
return Instantiate(item);
}
Transform candidate = pool.Pop();
candidate.gameObject.SetActive(true);
return candidate.gameObject;
}
public void ReturnObject(Transform trans)
{
// Use `DestroyImmediate` here if you don't need Pool
trans.SendMessage("ScrollCellReturn", SendMessageOptions.DontRequireReceiver);
trans.gameObject.SetActive(false);
trans.SetParent(transform, false);
pool.Push(trans);
}
public void ProvideData(Transform transform, int idx)
{
List<PackageLocalItem> items = GameManager.Instance.GetSortPackageLocalData();
PackagePanel uiParent = (PackagePanel)UIManager.Instance.GetPanel(UIConst.PackagePanel);
transform.GetComponent<PackageCell>().Refresh(items[idx], uiParent);
}
void Start()
{
var ls = GetComponent<LoopScrollRect>();
ls.prefabSource = this;
ls.dataSource = this;
ls.totalCount = GameManager.Instance.GetSortPackageLocalData().Count;
ls.RefillCells();
}
}
}
然後把這個腳本掛到 VerticalScroll_Grid 物件身上,把原本 demo 中的 InitOnStart 腳本刪了。
因為咱們的背包介面是從預製件中動態創建的,因此需要把 VerticalScroll_Grid 拷貝到 PackagePanel 中(替換原本的 ScrollView)
PackagePanel.cs 是背包系統介面的主要程式碼,這裡咱們把對原版 scrollView 初始化的邏輯註解掉,這樣之前的滾動容器就相當於失效了。
最後解決第二個問題,根據官方文檔,子物體需要做下小小的調整。
加上 Layout Element 這個組件,用來控制子物體的大小,其他都不用改。
這時候運行遊戲就可以看到,我們專案中的滾動容器已經替換為 LoopScrollView。
一些大小和間距可以在這裡調整
外層容器的大小:
內層容器的大小:
Unity LoopScrollRect 相關介紹 & 教學影片
Unity LoopScrollRect 滾動高手大師相關網站 & 插件下載點
【LoopScrollRect】
qiankanglai.me 插件作者個人部落格:优化 UGUI 的 ScrollRect
————————————————
以上內容改編節錄自:
qiankanglai.me 作者:Kanglai Qian
更多好用插件:【Unity 好用插件推薦】持續更新,一起讓遊戲開發事半功倍!
本文原創(或整理)於亞洲電玩通,未經作者與本站同意不得隨意引用、轉載、改編或截錄。
特約作家簡介
支持贊助 / DONATE
亞洲電玩通只是很小的力量,但仍希望為復甦台灣遊戲研發貢獻一點動能,如果您喜歡亞洲電玩通的文章,或是覺得它們對您有幫助,歡迎給予一些支持鼓勵,不論是按讚追蹤或是贊助,讓亞洲電玩通持續產出,感謝。
BTC |
352Bw8r46rfXv6jno8qt9Bc3xx6ptTcPze |
|
ETH |
0x795442E321a953363a442C76d39f3fbf9b6bC666 |
|
TRON |
TCNcVmin18LbnXfdWZsY5pzcFvYe1MoD6f |