# Load addressable assets bằng scripts

### Tóm tắt

Trong bài học này chúng ta sẽ tìm hiểu một vài cách để load addressable asset vào game và làm thế nào để giải phóng chúng khỏi bộ nhớ bằng scripts. Chúng tôi đã đính lèm một số tài liệu tham khảo và các phần ôn tập về những khái niệm lập trình mà bạn sẽ sử dụng với hệ thống Addressables.

Kết thúc bài này chúng ta sẽ:

- Định nghĩa một thao tác bất đồng bộ (asynchronous operation).
- Xác định các cách khác nhau để tham chiếu đến một addressable trong code
- Xác định các phương pháp khác nhau để load và release các addressable một cách bất đồng bộ.

### Tổng quan

Trong hướng dẫn này, bạn sẽ học cách load và unload addressable vào game thông qua code. Trong suốt bài thực hành này, bạn sẽ tiến hành loại bỏ phần lớn source code sử dụng Resource API trong dự án Loady Dungeons và thay bằng Addressables API.

### Trước khi bắt đầu

Trước khi bắt đầu chúng ta hãy cùng ôn tập lại những khái niệm quan trọng trong lập trình và đặc biệt cần thiết với Addressables.

#### Lập trình bất đồng bộ

Trong Addressables API, nhiều tác vụ thực hiện việc load asset và dữ liệu sau đó trả về kết quả. Khi các tài nguyên hoặc dữ liệu đó nằm trên một máy chủ, các tác vụ này sẽ cần thêm thời gian để hoàn thành. Để tránh làm giảm hiệu suất và gây trì hoãn các tác vụ khác. Hệ thống Addressables sử dụng các thao tác bất đồng bộ, điều này có nghĩa là nhiều tác vụ có thể chạy đồng thời.

<span style="white-space: pre-wrap;">Để triển khai các thao tác bất đồng bộ với Addressables API bạn sẽ cần hiểu rõ những khái niệm sau (những khái niệm này nằm trong project </span>[Intermediate scripting](https://learn.unity.com/project/intermediate-gameplay-scripting)):

- **Coroutines**<span style="white-space: pre-wrap;"> cho phép bạn thực thi một hàm trong nhiều frame thay vì chờ nó hoàn thành ngay trong một frame.</span>
- **Delegates** là các container đại diện cho các hàm có thể truyền đi dưới dạng tham số hoặc sử dụng như các biến thông thường.
- **Events** <span style="white-space: pre-wrap;">là một dạng delegate chuyên biệt, có khả năng phát tín hiệu thông báo cho class khác biết rằng có một sự kiện </span>

#### Structs

<span style="white-space: pre-wrap;">Addressables API sử dụng struct có tên là AsyncOperationHandle để giữ tác vụ bất đồng bộ bên trong code của bạn. </span>

Struct là một kiểu tham trị trong C#, có thể được sử dụng để lưu các dữ liệu liên quan với nhau thành một nhóm. Chúng giống với class ở chỗ có thể chứa các filed và các method nhưng khác với class chúng được truyền theo giá trị thay vì truyền theo tham chiếu. Có nghĩa là khi bạn tạo một struct và truyền vào một method hoặc gán nó cho một biến, một bản sao của struct sẽ được tạo ra thay vì tạo ra tham chiếu trỏ tới đối tượng gốc.

[Struct AsyncOperationHandle](https://docs.unity3d.com/Packages/com.unity.addressables@0.8/api/UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle.html)<span style="white-space: pre-wrap;"> có một giá trị bool để xác định xem method thành công hay thất bại và trả về giá trị sau quá trình load</span>

#### Tuần tự hóa (Serialization)

Tuần tự hóa là quá trình chuyển đổi và lưu trữ dữ liệu giữa các phiên làm việc (sesstion) của một ứng dụng. Giải tuần tự hóa (Deserialization) là quá trình trích xuất dữ liệu đã lưu trữ đó để nó có thể được tái cấu trúc lại khi ứng dụng chạy ở những lần sau.

<span style="white-space: pre-wrap;">Trong hướng dẫn này, bạn không cần trở thành một chuyên gia tuần tự hóa. Bạn chỉ cần biết rằng Unity thực hiện tuần tự hóa dữ liệu trong quá trình build để nó có thể giải tuần tự hóa dữ liệu được lưu trữ đó khi ứng dụng chạy. Thuộc tính </span>[\[SerializeField\]](https://docs.unity3d.com/ScriptReference/SerializeField.html)<span style="white-space: pre-wrap;"> có tác dụng đánh dấu các trường dữ liệu private là được tuần tự hóa và các trường đã được tuần tự hóa sẽ có thể hiển thị trong Inspector để bạn có thể chỉnh sửa như các biến public.</span>

Khi bạn sử dụng Addressables, bạn sẽ phải sử dụng quá trình tuần tự hóa để tối ưu hóa cách dự án của bạn tiêu thụ tài asset.

<span style="white-space: pre-wrap;">Bạn có thể </span>[tìm hiểu thêm về tuần tự hóa trong Unity Manual.](https://docs.unity3d.com/Manual/script-Serialization.html)

### Load một addressable prefab

<span style="white-space: pre-wrap;">Dưới đây là một vài cách để load một addressable prefabs với Addressables API. Hãy bắt đầu với việc load asset bằng cách sử dụng địa chỉ của nó, điều mà bạn sẽ thực hiện trong quá trình chuyển đổi script </span>`<span class="editor-theme-code">PlayerConfigurator</span>`<span style="white-space: pre-wrap;"> sang sử dụng Addressable API.</span>

<span style="white-space: pre-wrap;">Để viết lại </span>`<span class="editor-theme-code">PlayerConfigurator.cs</span>`<span style="white-space: pre-wrap;"> với Addressables API thay vì Resources API, bạn có thể làm theo hướng dẫn dưới đây:</span>

1. <span style="white-space: pre-wrap;">Từ cửa sổ Project, mở file </span>`<span class="editor-theme-code">Assets > Scripts > PlayerConfigurator.cs</span>`
2. Khai báo các namespace của Addressable cần sử dụng  
      
    `<span class="editor-theme-code">using UnityEngine.AddressableAssets;</span>`  
    `<span class="editor-theme-code">using UnityEngine.ResourceManagement.AsyncOperations;</span>`  
      
    <span style="white-space: pre-wrap;">Bạn sẽ cần sử dụng namespace này để load asset thông qua địa chỉ của nó và truy cập vào </span>`<span class="editor-theme-code">AsyncOperationHandle</span>`<span style="white-space: pre-wrap;"> được trả về.</span>
3. Trong class PlayerConfigurator tạp một biến string mới đại diện cho địa chỉ. Đặt attribute \[SerializeField\] ở trước đó để bạn có thể serialize nó và chỉnh sửa giá trị ngay trong Inspector.  
      
    `<span class="editor-theme-code">[SerializeField] private string m_Address;</span>`  
      
    Sau khi khai báo bạn có thể thấy biến này xuất hiện trong của sổ Inspector với tên là Address
4. Trong script hãy tìm dòng code dưới đây. Nó đang sử dụng Resources API  
      
    `<span class="editor-theme-code">private ResourceRequest m_HatLoadingRequest;</span>`  
      
    Và thay nó bằng dòng code sử dụng Addressables API dưới đây:  
      
    `<span class="editor-theme-code">private AsyncOperationHandle<GameObject> m_HatLoadOpHandle;</span>`  
      
    <span style="white-space: pre-wrap;">Dòng mới này khai váo biến </span>`<span class="editor-theme-code">m_HatLoadOpHandle</span>`<span style="white-space: pre-wrap;"> là một </span>`<span class="editor-theme-code">AsyncOperationHandle</span>`<span style="white-space: pre-wrap;"> của GameObject.</span>
5. <span style="white-space: pre-wrap;">Trong hàm </span>`<span class="editor-theme-code">SetHat()</span>`<span style="white-space: pre-wrap;"> hãy xóa toàn bộ phần thân của nó đi và thay bằng dòng code dưới đây</span>  
      
    `<span class="editor-theme-code">m_HatLoadOpHandle = Addressables.LoadAssetAsync<GameObject>(m_Address);</span>`  
      
    <span style="white-space: pre-wrap;">Dòng code này sẽ load bất đồng bộ prefab thông qua địa chỉ </span>`<span class="editor-theme-code">m_Address</span>`<span style="white-space: pre-wrap;">. Biến </span>`<span class="editor-theme-code">m_HatLoadOpHandle</span>`<span style="white-space: pre-wrap;"> sẽ chứa một giá trị bool cho biết asset có được tải thành công hay không. Nếu load thành công, handle này cũng chứa kết quả của request tức là chính prefab chúng ta đang load.</span>
6. <span style="white-space: pre-wrap;">Bởi vì asset được load bất đồng bộ, nên các dòng code phía sau nó sẽ được chạy mà không cần chờ </span>`<span class="editor-theme-code">LoadAssetAsync</span>`<span style="white-space: pre-wrap;"> chạy xong. Vậy nên để biết được việc load có thành công hay không chúng ta phải đăng ký một event để biết khi nào load xong.</span>  
      
    `<span class="editor-theme-code">m_HatLoadOpHandle.Completed += OnHatLoadComplete;</span>`  
      
    <span style="white-space: pre-wrap;">Dòng code này dùng để đăng ký một event handler (chúng ta sẽ khai báo nó ở bước sau). Vì chúng ta cần biết khi nào asset được load xong nên ở đây chúng ta sử dụng event </span>`<span class="editor-theme-code">Completed</span>`<span style="white-space: pre-wrap;"> của </span>`<span class="editor-theme-code">AsyncOperationHandle</span>`<span style="white-space: pre-wrap;"></span>
7. <span style="white-space: pre-wrap;">Ở đây chúng ta sẽ viết hàm </span>`<span class="editor-theme-code">OnHatLoadComplete()</span>`<span style="white-space: pre-wrap;"> thay cho hàm </span>`<span class="editor-theme-code">OnHatLoaded()</span>`<span style="white-space: pre-wrap;"> để in ra trạng thái của handle. Hàm này được gọi bởi Addressables khi nó thực hiện xong yêu cầu tải prefab ở trên.</span>  
      
    `<span class="editor-theme-code">private void OnHatLoadComplete(AsyncOperationHandle<GameObject> asyncOperationHandle)</span>`  
    `<span class="editor-theme-code">{</span>`  
    `<span class="editor-theme-code">    Debug.Log($"AsyncOperationHandle Status: {asyncOperationHandle.Status}");</span>`  
    `<span class="editor-theme-code">}</span>`
8. <span style="white-space: pre-wrap;">Trong hàm OnDisable(), xóa bỏ toàn bộ phần body và thay thế bằng dòng code dưới đây để hủy đăng ký sự kiện khi gameObject bị disable. Nó sẽ chặn biệc Addressables gọi </span>`<span class="editor-theme-code">OnHatLoadComplete()</span>`<span style="white-space: pre-wrap;"> khi </span>`<span class="editor-theme-code">PlayerConfigurator</span>`<span style="white-space: pre-wrap;"> không hoạt động.</span>  
      
    `<span class="editor-theme-code">m_HatLoadOpHandle.Completed -= OnHatLoadComplete;</span>`
9. Save script và quay lại Unity.
10. Để test script này, mở scene Assets &gt; Scenes &gt; Level\_00.
11. Trong cửa sổ Project, chọn prefab Assets &gt; Prefabs &gt; Player.
12. <span style="white-space: pre-wrap;">Nhìn sang cửa sổ Inspector, tìm component </span>`<span class="editor-theme-code">PlayerConfigurator</span>`<span style="white-space: pre-wrap;"> và đặt giá trị cửa biến Address thành địa chỉ của chiếc mũ. Ở phần trên chúng ta đã đặt là Hat\_BunnyEars.</span>
13. <span style="white-space: pre-wrap;">Play game và nhìn vào cửa sổ Console, bạn sẽ thấy dòng thông báo </span>**"AsyncOperationHandle Status: Succeeded"**.  
    Tại thời điểm này, asset được được load thành công. Tuy nhiên, bạn sẽ chưa nhìn thấy nó trên scene vì bạn chưa instantiate nó.
14. <span style="white-space: pre-wrap;">Giờ là lúc bạn instantiate prefab mà bạn vừa load lên. Viết lại hàm </span>`<span class="editor-theme-code">OnHatLoadComplete()</span>`<span style="white-space: pre-wrap;"> như dưới đây:</span>  
      
    `<span class="editor-theme-code">private void OnHatLoadComplete(AsyncOperationHandle<GameObject> asyncOperationHandle)</span>`  
    `<span class="editor-theme-code">{</span>`  
    `<span class="editor-theme-code">    if (asyncOperationHandle.Status == AsyncOperationStatus.Succeeded)</span>`  
    `<span class="editor-theme-code">    {</span>`  
    `<span class="editor-theme-code">        Instantiate(asyncOperationHandle.Result, m_HatAnchor);</span>`  
    `<span class="editor-theme-code">    }</span>`  
    `<span class="editor-theme-code">}</span>`
15. Vào lại Play mode và bạn sẽ thấy player di chuyển cùng cái mũ.
16. Bạn có thể thử với những địa chỉ khác mà chúng ta đã tạo ở bài hướng dẫn trước.

<p class="callout warning">Lưu ý: Trong ví dụ này Address là một string và nó sẽ không xác thực xem bạn có nhập đúng địa chỉ hay không. Bạn chỉ biết khi bạn thực sự chạy load. Bạn có thể nhập bất cứ thứ gì và điều đó có thể gây ra lỗi khi chạy game. Hãy đảm bảo rằng bạn nhập chính xác địa chỉ của prefab muốn load.</p>

### Load một addressable prefab với AssetReference

Asset Reference là một kiểu dữ liệu dùng để tham chiếu đến addressable asset. Nó được thiết kế để sử dụng như một trường dữ liệu có thể serializable trong MonoBehaviour hay ScriptableObject. Khi bạn thêm một AssetReference vào trong các class này, bạn có thể gán một địa chỉ cho nó trong cửa sổ Inspector thông qua một công cụ chọn. Những lựa chọn này được giới hạn nghiêm ngặt, chỉ cho phép chọn các asset đã được đánh dấu là addressable.

Về mặt hình thức, bạn sẽ khai báo các AssetReference vào script giống hệt như khi bạn sử dụng tham chiếu trực tiếp, tức là có thể thông qua các trường public hoặc các trường private có sử dụng attribute \[SerializableField\]. Tuy nhiên, AssetReference không lưu trữ trực tiếp một tham chiếu đến asset. Thay vào đó, AssetReference lưu trữ mã định danh GUID của asset đó. GUID này được hệ thống Addressables sử dụng để lưu trữ và truy xuất đối tượng trong runtime.

Lợi ích cốt lõi của việc sử dụng AssetReference so với việc dùng địa chỉ dạng string là nó bắt buộc bạn chỉ được chọn asset thông qua Inspector. Điều này giúp trành các rủi ro như không đánh có như sai chính tả...

<span style="white-space: pre-wrap;">Tiếp theo chúng ta sẽ viết lại </span>`<span class="editor-theme-code">PlayerConfigurator</span>`<span style="white-space: pre-wrap;"> sử dụng AssetReference thay cho địa chỉ dạng string.</span>

1. <span style="white-space: pre-wrap;">Vẫn là mở file </span>`<span class="editor-theme-code">Assets > Scripts > PlayerConfigurator.cs</span>`
2. <span style="white-space: pre-wrap;">Thay </span>`<span class="editor-theme-code">m_Address</span>`<span style="white-space: pre-wrap;"> bằng dòng dưới đây:</span>  
      
    `<span class="editor-theme-code">[SerializeField] private AssetReference m_HatAssetReference;</span>`
3. <span style="white-space: pre-wrap;">Trong hàm </span>`<span class="editor-theme-code">SetHat()</span>`<span style="white-space: pre-wrap;"> chúng ta thay phần body dòng code sau để load asset: </span>  
      
    `<span class="editor-theme-code">if (!m_HatAssetReference.RuntimeKeyIsValid()) return;</span>`  
    `<span class="editor-theme-code">m_HatLoadOpHandle = m_HatAssetReference.LoadAssetAsync<GameObject>();</span>`  
    `<span class="editor-theme-code">m_HatLoadOpHandle.Completed += OnHatLoadComplete;</span>`  
      
    `<span class="editor-theme-code">RuntimeKeyIsValid</span>`<span style="white-space: pre-wrap;"> thực hiện kiểm tra xem các giá trị được chọn có phải là một địa chỉ hợp lệ không. Trong trường hợp này nó sẽ chặn </span>`<span class="editor-theme-code">LoadAssetAsync</span>`<span style="white-space: pre-wrap;"> nếu </span>`<span class="editor-theme-code">AssetReference</span>`<span style="white-space: pre-wrap;"> bị bỏ trống (None);</span>
4. Save script và quay trở lại với Level\_00;
5. Trong cửa sổ Project, chọn prefab Assets &gt; Prefabs &gt; Player.
6. Trong cửa sổ Inspector, tìm đến component PlayerConfigurator. Bây giờ trường Address đã được thay thế bằng trường Hat Asset Reference. Tại đây hãy chọn cái mũ mà bạn muốn.
7. Play game và xem nhân vật có di chuyển cùng cái mũ bạn chọn không.
8. Giờ bạn có thể thử chọn các AssetReference khác và quan sát kết quả.

<span style="white-space: pre-wrap;">Bạn cũng có thể đọc thêm về AssetReference </span>[tại đây](https://docs.unity3d.com/Packages/com.unity.addressables@1.19/manual/AssetReferences.html).

### Load một addressable prefab với AssetReferenceGameObject

Công cụ chọn của AssetReference cho phép chọn tất cả các asset được đánh dấu là addressable nên bạn có thể thấy nó cho phép bạn chọn cả texture, scene... Mặc dù được phép chọn nhưng nếu bạn cố load chúng như những GameObject nó sẽ gây ra lỗi trong code của Addressables.

<span style="white-space: pre-wrap;">Bạn có thể muốn quản lý chặt chẽ hơn nữa và chỉ cho phép chọn các prefab từ công cụ chọn. Để làm được điều này bạn có thể sử dụng </span>`<span class="editor-theme-code">AssetReferenceGameObject</span>`<span style="white-space: pre-wrap;"> thay vì </span>`<span class="editor-theme-code">AssetReference</span>`.

<span style="white-space: pre-wrap;">Tiếp tục viết lại </span>`<span class="editor-theme-code">PlayerConfigurator</span>`<span style="white-space: pre-wrap;"> nhé.</span>

1. <span style="white-space: pre-wrap;">Mở file </span>`<span class="editor-theme-code">Assets > Scripts > PlayerConfigurator.cs</span>`
2. <span style="white-space: pre-wrap;">Thay </span>`<span class="editor-theme-code">m_HatAssetReference</span>`<span style="white-space: pre-wrap;"> bằng dòng dưới đây:</span>  
      
    `<span class="editor-theme-code">[SerializeField] private AssetReferenceGameObject m_HatAssetReference;</span>`
3. Save script và quay trở lại Unity
4. Trong cửa sổ Project, chọn prefab Assets &gt; Prefabs &gt; Player.
5. Trong cửa sổ Inspector, tìm đến component PlayerConfigurator. Mở picker lên và chọn một cái mũ bạn muốn. Chú ý xem hiện giờ bạn không thể chọn LoadyDungeonsLogo hay LoadingScene nữa.  
    [![image.png](https://knowledge.thanhdv.com/uploads/images/gallery/2026-04/scaled-1680-/KjCimage.png)](https://knowledge.thanhdv.com/uploads/images/gallery/2026-04/KjCimage.png)
6. Play game và thử cái mũ mới.

<span style="white-space: pre-wrap;">Bạn cũng có thể đọc thêm về AssetReferenceGameObject </span>[tại đây](https://docs.unity3d.com/Packages/com.unity.addressables@1.20/manual/AssetReferences.html).