Skip to content

omar92/Optmized-unity-scroll-list

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

36 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Optimized Unity Scroll List

A high-performance, pooling-based scroll list system for Unity that efficiently renders 1000+ items in a ScrollView with minimal draw calls and smooth 60+ FPS scrolling, even on low-end devices.

Features

  • πŸš€ Virtual Scrolling / Object Pooling β€” only the visible items (plus a small buffer) are instantiated at any time, regardless of the total list size
  • πŸ“‰ Drastically Reduced Draw Calls β€” keeps the batch count low by recycling a fixed pool of GameObjects instead of creating one per item
  • πŸ“ Responsive Layout β€” automatically adapts to screen/viewport size changes
  • ↔️ Horizontal & Vertical Support β€” works with both scroll directions
  • πŸ”” Lifecycle Callbacks β€” OnItemShow / OnItemHide actions and UnityEvents let you update item data cleanly
  • 🧩 ScriptableObject Architecture β€” decoupled data (ScriptableVariable<T>) and event (EventSO) system for clean, inspector-driven communication

Project Structure

Assets/
β”œβ”€β”€ Prefabs/                        # UI prefabs (inventory item, panels)
β”œβ”€β”€ Resources/
β”‚   β”œβ”€β”€ Data/                       # ScriptableObject assets
β”‚   β”‚   β”œβ”€β”€ SelectedItem.asset      # IntSO β€” currently selected item index
β”‚   β”‚   β”œβ”€β”€ SelectedData.asset      # InventoryItemsDataSO β€” full item array
β”‚   β”‚   β”œβ”€β”€ CurrentSprite.asset     # SpriteSO β€” current item sprite
β”‚   β”‚   └── events/                 # EventSO assets (data ready, item selected)
β”‚   └── Sprites/
β”œβ”€β”€ Scenes/
β”‚   └── Main.unity                  # Main demo scene
└── Scripts/
    β”œβ”€β”€ OptmizedList/
    β”‚   β”œβ”€β”€ ListOptmizer.cs         # Core virtual-scroll / pooling engine
    β”‚   └── OptmizedListItem.cs     # Per-item show/hide UnityEvent hooks
    β”œβ”€β”€ MonoBehaviours/
    β”‚   β”œβ”€β”€ InventoryManager.cs     # Loads JSON, generates item data
    β”‚   β”œβ”€β”€ InventoryHandler.cs     # Connects manager β†’ ListOptmizer
    β”‚   β”œβ”€β”€ InventoryItem.cs        # Individual item UI logic
    β”‚   β”œβ”€β”€ InventoryInfoPanel.cs   # Info panel (name, description, stat)
    β”‚   β”œβ”€β”€ InventoryItemImage.cs   # Info panel sprite display
    β”‚   └── AspectRatioFitterImageAdjuster.cs
    └── scriptableSystem/
        β”œβ”€β”€ ScriptableVariable.cs   # Generic ScriptableObject data container
        β”œβ”€β”€ IntSO.cs / SpriteSO.cs / InventoryItemsDataSO.cs
        β”œβ”€β”€ EventSO.cs              # ScriptableObject-based event bus
        └── EventSOListener.cs      # MonoBehaviour subscriber for EventSO

Requirements

  • Unity 2020.3 LTS or later (the project uses UGui, TextMesh Pro)
  • TextMesh Pro package (included via Packages/manifest.json)

Getting Started

1. Open the Project

  1. Clone or download this repository.
  2. Open Unity Hub and click Add β†’ select the repository root folder.
  3. Open the project with a compatible Unity version.

2. Open the Demo Scene

Navigate to Assets/Scenes/Main.unity and open it. Press Play to see the optimized scroll list in action with 1000+ inventory items.


How to Use ListOptmizer in Your Own Scene

Step 1 β€” Set Up the ScrollRect

  1. Create a ScrollRect GameObject in your Canvas (e.g., via GameObject β†’ UI β†’ Scroll View).
  2. Add the ListOptmizer component to that same ScrollRect GameObject.
    • It requires a ScrollRect component on the same object (enforced by [RequireComponent]).

Step 2 β€” Assign the Item Template

  1. Create (or use an existing) item prefab β€” this is the GameObject that will be cloned and recycled to display each row.
  2. In the ListOptmizer Inspector, assign your prefab to the Item Template field.
    • The template is hidden at runtime; ListOptmizer activates/deactivates it as needed.

Tip: Make sure the item prefab has a fixed size in the scroll direction so ListOptmizer can calculate how many items fit in the viewport.

Step 3 β€” Call PopulateList from Code

// Example: populate with 1000 items
[SerializeField] private ListOptmizer listOptmizer;

void Start()
{
    listOptmizer.PopulateList(
        itemsNumber: 1000,
        OnItemShow: (index, gameObject) =>
        {
            // Called every time an item becomes visible.
            // Update the item's UI here using `index`.
            var item = gameObject.GetComponent<MyItemComponent>();
            item.SetData(myDataArray[index]);
        },
        OnItemHide: (index, gameObject) =>
        {
            // Optional: called when an item scrolls out of view.
        }
    );
}
Parameter Type Description
itemsNumber int Total number of items in the list
OnItemShow Action<int, GameObject> Callback fired when an item becomes visible; receives the item's data index and its GameObject
OnItemHide Action<int, GameObject> (Optional) Callback fired when an item scrolls out of view

Step 4 β€” (Optional) Use OptmizedListItem for Inspector-Wired Events

If you prefer wiring callbacks in the Inspector instead of code, add the OptmizedListItem component to your item prefab and connect the OnShow / OnHide UnityEvents:

Item Prefab
└── OptmizedListItem (component)
    β”œβ”€β”€ OnShow (UnityEvent<int>)  β†’ MyItemComponent.SetData
    └── OnHide (UnityEvent<int>)  β†’ (optional cleanup)

OnShow receives the item's data index as its int argument.

Step 5 β€” Iterate Over Visible Items

At any point you can iterate over the currently visible pool:

listOptmizer.ForEachVisible((index, gameObject) =>
{
    // e.g., refresh highlight state after a selection change
    gameObject.GetComponent<MyItemComponent>().SetSelected(index == selectedIndex);
});

How It Works Internally

ListOptmizer implements a virtual scrolling strategy:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ ScrollRect Content ────────────────────────────┐
β”‚  [rearFiller]   ← empty RectTransform that simulates hidden items above β”‚
β”‚  [Item Pool 0]                                                           β”‚
β”‚  [Item Pool 1]  ← only ~(viewport / itemHeight) + 2 items exist         β”‚
β”‚  [Item Pool 2]                                                           β”‚
β”‚      ...                                                                 β”‚
β”‚  [frontFiller]  ← empty RectTransform that simulates hidden items below  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  1. On PopulateList β€” calculates how many items fit in the viewport, instantiates exactly that many GameObjects (plus 2), and creates invisible rearFiller / frontFiller RectTransforms.
  2. On scroll β€” OnScrollValueChanged calculates the new startIndex/endIndex, resizes the fillers to represent the hidden items, and recycles items that have scrolled off one edge by moving them to the opposite edge and invoking the OnItemShow callback with the new index.
  3. On viewport resize β€” OnRectTransformDimensionsChange recalculates all measures and rebuilds the pool to fit the new size.

ScriptableObject Architecture

The demo uses a lightweight, asset-based architecture to keep components decoupled:

Data Containers (ScriptableVariable<T>)

Store shared state as Unity assets. Any MonoBehaviour can read/write them without direct references to other MonoBehaviours.

Asset Type Purpose
SelectedItem IntSO Index of the currently selected inventory item
SelectedData InventoryItemsDataSO Array of all InventoryItemData structs
CurrentSprite SpriteSO Sprite of the currently selected item

Event Bus (EventSO / EventSOListener)

EventSO is a ScriptableObject that acts as a named event channel. Any component can call Raise() on it; any EventSOListener subscribed to it will fire its configured callback.

// Raise programmatically
[SerializeField] private EventSO onItemSelected;
onItemSelected.Raise();

// Or press the "Raise" button in the Inspector during Play mode (EventSOEditor)

EventSOListener subscribes/unsubscribes automatically via OnEnable / OnDisable, so it is safe to use on pooled objects.


Inventory Demo β€” Key Components

Component GameObject Role
InventoryManager Manager Parses item JSON, populates InventoryItemsDataSO, raises OnDataIsReady
InventoryHandler Handler Listens for OnDataIsReady, calls ListOptmizer.PopulateList
InventoryItem Item Prefab Updates icon/name UI; raises OnItemSelected when clicked
InventoryInfoPanel InfoPanel Listens for OnItemSelected, shows name / description / stat
InventoryItemImage InfoPanel Listens for OnItemSelected, shows item sprite

Data flow on startup

InventoryManager.Start()
  └─ Generates InventoryItemData[] from JSON
  └─ Raises OnDataIsReady
        └─ InventoryHandler.OnDataIsReady()
              └─ ListOptmizer.PopulateList(count, OnItemShow β†’ InventoryItem.SetData)
                    └─ Builds visible pool, fires OnItemShow for each visible item
        └─ InventoryInfoPanel.OnDataReady()  ← displays item[0] on startup

Data flow on item click

InventoryItem.OnClick()
  └─ selectedItemIndex.Value = index   (writes IntSO)
  └─ Raises OnItemSelected
        └─ InventoryInfoPanel.OnItemSelected() β†’ updates text
        └─ InventoryItemImage.OnItemSelected() β†’ updates sprite
        └─ InventoryItem.OnItemClicked()       β†’ refreshes highlight (all visible items)

Performance Notes

Scenario Without optimization With ListOptmizer
1000 items ~1000 UI GameObjects ~12–16 UI GameObjects
Draw call batches Very high (scales with item count) Low (scales with viewport size only)
Memory O(n) O(viewport)
FPS (low-end device) <30 FPS while scrolling 60+ FPS

License

This project is provided as an open-source reference implementation. Feel free to use and adapt it in your own Unity projects.

About

Optimize-Test

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors