The use and optimization of ScrollView in Unity development UGUI module development experience

-Preface-

It's been a long time since I blogged. I recently started the development of Unity. At the beginning, I was doing UI writing logic. At present, the main task is to understand the module development of Unity UI. In this chapter, let's learn about the ScrollView function that has been used more recently.

The ScrollView function in Unity is a single scrolling area, but in our daily game development, the functions required to use ScrollView are more like using List. The View is composed of repeated prefabs, and items with different contents are displayed according to different data. In fact, if you don't consider the optimization of performance and personality, I think that Unity's ScrollView is very powerful, and it can easily achieve the required functions with automatic layout.

-text-

-Implementation regardless of performance-

First, create a new ScrollView component in the scene, and Unity will automatically generate it for us as shown in the figure

Content is the root node of our element addition, we can instantiate Prefabs through GameObject and add them to Content. There is nothing to say about adding logic. Say the next two points of attention:

1. Since the scrolling content is self-adapted according to the height of the content, when we add a child node under the content, the height of the content needs to be updated. There is a way that does not require us to calculate, which is to mount the ContentSizeFitter script on the content

2. Mount the adaptive script according to the requirements, select one of GridLayoutGroup, VerticalLayoutGroup, and HorizontalLayoutGroup that is suitable for actual development, and set the parameters.

question

There is a big problem with the above method:

1. Rendering pressure: For items that are not displayed, Unity will also remember to calculate and render. Although I don’t know the UnityUI rendering steps for the time being, ScrollView is the Mask mask used. Mask is generally calculated and submitted for rendering, but only in the vertex shader. clipped in

2. Calculation pressure: If the ScrollView is a player’s backpack, there may be 1,000 pieces of backpack data. If the instance processes so much data and UI assignment at the same time in 1 and 2 frames, it will be very stuck and affect the game experience. If special circumstances do not Doing optimization generally requires adding a progress bar.

Optimization method

The optimization method for ScrollView is similar no matter what engine or language it is. It is to calculate the source data interval required to display the item in a Viewport window according to the current Bar value. The following code is written based on the x-lua framework, and only realizes vertical sliding, which means the same, as long as you can understand it.

To realize this ScrollView, we will distinguish it from the original name and name it ListView, but it is still ScrollView in its core.

1. Create ListView data

local list_db = {
    -- 布局信息
    Layout = {
        Padding = {
            Left = 10,
            Right = 10,
            Top = 30,
            Bottom = 30
        },
        CellSize = {
            x = 170, y = 174
        },
        Spacing = {
            x = 10, y = 10
        }
    },
    -- prefabs路径
    PrefabsPath = false,
    -- 逻辑类
    LogicClass = false
}

This ListView needs to be like the Layout layout information that comes with Unity, so that it is convenient to set the coordinates when setting the item.

2. Create a ListView

-- 创建
UIListView.OnCreate = function(self, list_model)
    base.OnCreate(self)

    self.unity_scroll_view = UIUtil.FindComponent(self.transform, typeof(CS.UnityEngine.UI.ScrollRect))
    if IsNull(self.unity_scroll_view) then
        Logger.LogError("Unity Scroll View is Null-->??")
    end
    self.content_trans = self.unity_scroll_view.content
    self.unity_scroll_bar = self.unity_scroll_view.verticalScrollbar
    -- 检测item是否加载
    if not GameObjectPool:GetInstance():CheckHasCached(list_model.PrefabsPath) then
        GameObjectPool:GetInstance():CoPreLoadGameObjectAsync(list_model.PrefabsPath, 1)
    end
    self.logic_cls = list_model.LogicClass
    self.render_prefabs_path = list_model.PrefabsPath
    self.cache_prefabs = {}
    self.list_model = list_model
    __InitCalColAndRowNum(self)
end

The list_model here is an instance of the list_db above

3. Calculate the required rows and columns in the viewport

-- 计算有多少列
local function __InitCalColAndRowNum(self)
    --计算列
    local layout = self.list_model.Layout
    local valid_width = self.content_trans.rect.width - layout.Padding.Left - layout.Padding.Right
    local valid_height = self.unity_scroll_view.viewport.rect.height - layout.Padding.Top - layout.Padding.Bottom
    -- 列
    self.col = Mathf.Floor(valid_width / (layout.CellSize.x + layout.Spacing.x))
    -- 最大实例化出来的行数,超过的动态算
    self.max_row = Mathf.Ceil((valid_height + layout.Spacing.y) / (layout.CellSize.y + layout.Spacing.y))
    self.valid_height = valid_height
end

Because of the Vertical direction here, the columns are fixed, and max_row is the largest row that can be accommodated in the viewport. The multiplication of this row and column is the minimum number of Prefabs required.

4. Set the data source datasource

The list is driven by data, how many items are needed after how much data is there, so here is based on the datasource passed in from the outside, here it does not matter what the data type is, but the requirement is an array.

-- 计算列表高度
local function __CalContentHeight(self)
    local layout = self.list_model.Layout
    local total_row = Mathf.Ceil(#self.data_source / self.col)
    local height = total_row * (layout.CellSize.y + layout.Spacing.y) + layout.Padding.Top + layout.Padding.Bottom - layout.Spacing.y
    self.content_trans.sizeDelta  = Vector2.New(0,height)
    self.total_row = total_row
end

-- 刷新list
local function __Refresh(self)
    local new_index_vec = __CalDataIndexIntervalByScrollbarValue(self)
    if self.index_vec == new_index_vec then
        return
    end
    self.index_vec = new_index_vec
    --__MoveAllToCache(self)
    if not self.using_prefabs then
        self.using_prefabs = {}
    end
    local layout = self.list_model.Layout
    local item_index = 1
    local allFromUsing = true
    for index = new_index_vec.x, new_index_vec.y do
        local data = self.data_source[index]
        local item
        if allFromUsing and #self.using_prefabs > item_index then
            item = self.using_prefabs[item_index]
            item_index = item_index + 1
            if self.render_handler then
                self.render_handler:RunWith(item,data)
            end
        elseif #self.cache_prefabs > 0 then
            allFromUsing = false
            item = table.remove(self.cache_prefabs, 1)
            item:SetActive(true)
            if self.render_handler then
                self.render_handler:RunWith(item, data)
            end
            table.insert(self.using_prefabs, item)
        else
            allFromUsing = false
            local go = GameObjectPool:GetInstance():GetLoadedGameObject(self.render_prefabs_path)
            if IsNull(go) then
                Logger.LogError("UIListView GetLoadedGameObject Fail-->>Path:" .. self.render_prefabs_path)
                return
            end
            local go_trans = go.transform
            go_trans:SetParent(self.content_trans)
            go_trans.anchorMin = Vector2.New(0,1)
            go_trans.anchorMax = Vector3.New(0,1)
            go_trans.localPosition = Vector3.zero
            go_trans.localScale = Vector3.one
            go_trans.name = self.logic_cls.__cname .. tostring(RenderItemCnt)
            RenderItemCnt = RenderItemCnt + 1
            item = self:AddComponent(self.logic_cls, go)
            item.transform.sizeDelta = Vector2.New(layout.CellSize.x,layout.CellSize.y)
            if self.render_handler then
                self.render_handler:RunWith(item, data)
            end
            item:SetActive(true)
            table.insert(self.using_prefabs, item)
        end
        item.transform.anchoredPosition = Vector2.New(__CalPositionByIndex(self,index))
    end
    if allFromUsing and #self.using_prefabs > item_index then
        for i = item_index, #self.using_prefabs do
            local tmp_item = self.using_prefabs[item_index]
            table.remove(self.using_prefabs,item_index)
            table.insert(self.cache_prefabs,tmp_item)
        end
    end
end

First, calculate the height of the content that needs to be set through the array length of the datasource. The function is similar to ContentSizeFitter, but here is calculated by yourself. Next, refresh the list. First, you need to calculate the starting point index and ending point index of the datasource according to the value of the current scrollbar. The calculation method is as follows

local function __CalDataIndexIntervalByScrollbarValue(self)
    local start_row = Mathf.Floor((1 - self.scroll_value) * self.total_row)
    local end_row = start_row + self.max_row
    local start_index = (start_row - 3) * self.col + 1
    local end_index = end_row * self.col
    if start_index < 1 then
        start_index = 1
    end
    if end_index > #self.data_source then
        local diff = end_index - #self.data_source
        end_index = #self.data_source
        start_index = start_index - diff
        if start_index < 1 then
            start_index = 1
        end
    end
    return Vector2.New(start_index,end_index)
end

First of all, we know how many lines are needed in total. According to the value, we can get how many lines are currently in, plus how many lines are needed for the viewport content, and convert it to index.

5. Calculate the coordinates according to the index of the data

-- 通过index计算坐标
local function __CalPositionByIndex(self,index)
    local layout = self.list_model.Layout
    local col = (index - 1) % self.col
    local row = Mathf.Floor((index - 1) / self.col)
    local x = col * (layout.CellSize.x + layout.Spacing.x) + layout.Padding.Left + layout.CellSize.x / 2
    local y = row * (layout.CellSize.y + layout.Spacing.y) + layout.Padding.Top + layout.CellSize.y / 2
    y = y * -1
    return x,y
end

Here you need to use the layout data in the previous list_db to set the specific coordinates

6. Callback rendering

After setting, you can call back to the holder of the ListView through a render function set before, tell it what item I use and what data, and let it use this data and item object to do some logic.

 

over~

I have only just learned Unity for a short time, and I am still exploring. If you have any questions, please correct me, thank you~

Guess you like

Origin blog.csdn.net/weixin_36719607/article/details/106203000