17 KiB
Addressables — Bootstrap & tóm tắt nghiệp vụ
Tài liệu này tóm tắt ghi chép Addressables (catalog / cache / remote) và các script runtime trong project perfect-world-unity: AddressablesCatalogUpdater, AddressablesRuntimeUrlRewriter, GameContentBootstrap, cùng chỉnh sửa AddressableManager.
Tham chiếu thêm: bản ghi chép tổng quan ban đầu (Addressables-Tong-hop.md nếu có trong repo hoặc bản copy ngoài project).
0. Hai scene — Content trước, Bootstrap sau (khuyến nghị)
Tách tải Addressables / catalog / bulk khỏi scene game để Bootstrap.unity (UI, CECUIManager, AddressableManager, …) không bị ảnh hưởng.
flowchart LR
A[GameContentBootstrap.unity index 0] -->|sync OK LoadScene Single| B[Bootstrap.unity]
B -->|BootstrapSceneController ~1s| C[LoginScene]
| Scene | Build index | Chứa gì |
|---|---|---|
| GameContentBootstrap | 0 | Chỉ GameContentBootstrap (+ UI loading tuỳ chọn) |
| Bootstrap | 1 | BootstrapSceneController, AddressableManager, CECUIManager, … không gắn GameContentBootstrap |
| LoginScene | … | Load từ BootstrapSceneController._nextSceneName như cũ |
Setup một lần trong Editor: menu Perfect World → Addressables → Setup Two-Scene Bootstrap
- Tạo
Assets/PerfectWorld/Scene/GameContentBootstrap.unity(nếu chưa có) - Gỡ
GameContentBootstrapkhỏiBootstrap.unity - Đưa scene Content lên index 0 Build Settings
Trên component GameContentBootstrap (scene Content):
| Field | Gợi ý |
|---|---|
_loadNextSceneAfterSuccess |
Bật |
_nextSceneName |
Bootstrap (tên scene trong Build Settings) |
_stayOnSceneWhenSyncFails |
Bật — lỗi thì không vào game |
Sau khi sync thành công: GameContentBootstrapSession.IsContentReady = true, Addressables đã init → scene Bootstrap chỉ cần AddressableManager / AUIManager gọi AddressablesInitService (không chờ gate).
Cấu hình component (GameContentBootstrap)
Hai chế độ
| Chế độ | _useServerForVersionInfo |
Bạn làm gì |
|---|---|---|
| Test / không API | Tắt | Chỉ cần hardcode (bảng dưới). Không cần _versionEndpointUrl. |
| Production | Bật | Điền URL API + (tuỳ chọn) POST body. Server sau khi kiểm tra dữ liệu trả JSON có contentVersion và (nếu cần) assetsBaseUrl. |
Editor: bật _skipRemoteCallInEditor nếu muốn không gọi mạng trong Editor; lúc đó luôn dùng hardcode / _editorFakeContentVersion như checklist cuối tài liệu.
Các biến cần biết (theo thứ tự ưu tiên đọc)
| Biến | Khi nào cần | Điền gì (ngắn gọn) |
|---|---|---|
_useServerForVersionInfo |
Luôn quyết định trước | Test → tắt. Live → bật. |
_hardcodedContentVersion |
Server tắt hoặc Editor skip | Chuỗi version (vd 1, 20250514). Đổi số này = game coi như có content mới → catalog + bulk download. |
_hardcodedAssetsBaseUrl |
Test CDN / không dùng server | Prefix URL thư mục remote Addressables trên CDN (vd https://my-cdn.com/PW/Android/). Phải khớp cấu trúc upload ServerData/<Platform>/. Để trống nếu bundle đã trỏ đúng URL lúc build, không cần đổi host. |
_versionEndpointUrl |
Chỉ khi server bật | URL đầy đủ một endpoint do backend cung cấp, vd https://api.game.com/v1/addressables/resolve. |
_usePostForVersionRequest |
Server bật | Bật nếu luồng của bạn là: client gửi JSON lên → server validate → trả link/prefix. Tắt = GET (không body). |
_versionPostBody |
POST bật | JSON một dòng hoặc nhiều dòng (vd {"platform":"Android","token":"..."}). Để trống → client gửi {}. |
_bakedRemoteUrlPrefixForRewrite |
Khi dùng assetsBaseUrl (từ server hoặc hardcode) |
Đúng phần đầu URL đã bake trong catalog (trùng Remote.LoadPath lúc build), vd https://old-build-cdn.example.com/Android/. Runtime thay prefix này bằng assetsBaseUrl. Để trống nếu không rewrite. Phải khớp đầu mọi InternalId remote cần đổi host. |
_remoteBulkDownloadLabel |
Gần như luôn | Trùng label trên group Addressables remote cần tải full (mặc định RemoteContent). |
_holdAddressablesInitUntilVersionChecked |
Khuyến nghị | Giữ bật để Addressables init sau khi có version + rewrite. |
_skipRemoteCallInEditor / _editorFakeContentVersion |
Editor | Bật skip + fake version khi không muốn HTTP trong Editor. |
_versionEndpointUrl vs _bakedRemoteUrlPrefixForRewrite — ví dụ một dòng
_versionEndpointUrl: không phải link tải từng bundle; là URL gọi API (GET hoặc POST) để lấy metadata:contentVersion+ tuỳ chọnassetsBaseUrl._bakedRemoteUrlPrefixForRewrite: không gọi trực tiếp; là chuỗi copy từ build (prefix URL trong catalog). Ví dụ build profileRemote.LoadPath=https://cdn-a.com/Android→ điền y hệt vào field này; server trảassetsBaseUrl=https://cdn-b.com/Android→ mọi bundle path đổi từcdn-asangcdn-b.
Luồng bạn muốn (POST → server kiểm tra → trả “link”)
- Bật
_useServerForVersionInfo, điền_versionEndpointUrl, bật_usePostForVersionRequest, điền_versionPostBody(vd token, platform, build channel). - Client POST
Content-Type: application/jsontới URL đó. - Server validate; nếu OK trả cùng format như trước (để
JsonUtilityparse được):
{
"contentVersion": "12",
"assetsBaseUrl": "https://cdn.example.com/PW/Android/"
}
assetsBaseUrl ở đây là base / prefix cho remote Addressables (nơi có catalog_*.json và bundle), không nhất thiết là một URL file đơn lẻ — Addressables vẫn dùng catalog + download theo label như cũ.
Option test: luôn dùng link hardcode để download
- Tắt
_useServerForVersionInfo. - Điền
_hardcodedContentVersion(vdtest1). - Nếu cần trỏ CDN test khác với URL đã bake trong catalog: điền
_hardcodedAssetsBaseUrlvà_bakedRemoteUrlPrefixForRewritenhư bảng trên. - Editor: có thể bật
_skipRemoteCallInEditorđể khỏi gọi server khi test trong Editor.
1. Khái niệm nhanh (catalog / bundle / cache)
| Thành phần | Vai trò |
|---|---|
Catalog (catalog_*.json + .hash) |
Danh mục: address → bundle, URL, hash/CRC… Catalog mới = tín hiệu “có nội dung mới”. |
| AssetBundle | Chứa dữ liệu asset thực tế. |
| Nhóm Local / Remote | Local: kèm build / StreamingAssets. Remote: tải qua HTTP(S). |
| Player / Editor | Bundle theo platform — không dùng bundle Windows cho Android. |
Cập nhật trên máy người chơi
- Catalog trỏ tới bundle/hash/CRC khác lần trước và luồng runtime thực hiện tải catalog mới / cập nhật catalog / load bundle thành công.
- Không tự cập nhật chỉ vì file trên server đổi nếu game không tải catalog mới hoặc không load đúng key.
Release(RAM): asset có thể hết trong bộ nhớ; lần load sau có thể đọc lại từ bundle đã cache trên đĩa — không đồng nghĩa “tải lại toàn bộ từ CDN”.
Cache bundle
- Nếu bundle đã cache hợp lệ theo catalog (CRC/hash đúng cấu hình), hệ thống ưu tiên dùng bản local, không tải lại cả file từ server.
Runtime đổi CDN / base URL
Remote.LoadPathtrong profile là lúc build (URL ghi vào catalog).- Đổi host/path lúc chạy: dùng
Addressables.InternalIdTransformFunc(rewriteInternalId) — trướcInitializeAsync/ load remote nhiều nhất có thể.
HTTP / HTTPS
- Player Settings: Allow downloads over HTTP nếu dùng
http://(thường chỉ dev). - Production: HTTPS; Android/iOS có thể chặn cleartext.
2. Script trong project
2.1. AddressablesCatalogUpdater.cs
Bọc API Addressables (package ~2.7.x):
EnsureInitializedAsyncCheckForCatalogUpdatesAsync/UpdateCatalogsAsync/CheckAndUpdateCatalogsIfNeededAsyncGetDownloadSizeBytesAsyncDownloadDependenciesAsyncCleanBundleCacheAsync
2.2. AddressablesRuntimeUrlRewriter.cs
InstallPrefixRewrite(fromPrefix, toPrefix)— thay prefix URL trongInternalId(CDN mới).ClearInstalledRewriteChainPreviousTransform— gọi transform cũ trước (nếu có).
2.3. GameContentVersionServerClient.cs
- Struct
GameContentVersionFetchResult(Ok,ContentVersion,AssetsBaseUrl,Error). GameContentVersionServerClient.FetchAsync(url, timeoutSeconds, usePost, postJsonBody)— GET hoặc POST JSON (Content-Type: application/json), parse JSON phẳng (JsonUtility).
2.4. GameContentBootstrap.cs (scene bootstrap)
Nguồn phiên bản / URL
_useServerForVersionInfo: tắt = không gọi server, dùng_hardcodedContentVersion/_hardcodedAssetsBaseUrl(test, API chưa chốt).- Bật = gọi
GameContentVersionServerClient.FetchAsynctới_versionEndpointUrl(GET hoặc POST theo_usePostForVersionRequest+_versionPostBody) (trừ khi Editor bật skip bên dưới). _skipRemoteCallInEditor: Editor không HTTP; ưu tiên hardcode nếu có version, không thì_editorFakeContentVersion.- Bật server nhưng URL trống → fallback hardcode (cảnh báo log).
Luồng
- Resolve version (server hoặc hardcode theo trên).
- Nếu có
assetsBaseUrlvà_bakedRemoteUrlPrefixForRewrite→InstallPrefixRewrite. - Mở gate → cho phép
AddressableManagergọiAddressables.InitializeAsync. EnsureInitializedAsync, so sánh version vớiPlayerPrefs:- Lần đầu hoặc
contentVersionkhác bản đã lưu →CheckAndUpdateCatalogsIfNeededAsync+DownloadDependenciesAsynctheo_remoteBulkDownloadLabel. - Trùng version (và không cần làm content work) → không catalog/bulk download.
- Lần đầu hoặc
PlayerPrefs
PW_GameContent_FirstRemoteSyncDonePW_GameContent_LastContentVersion
Event / API
Finished(BootstrapResult:Success,DidContentWork,ErrorMessage,ServerContentVersion).RunInternalAsync()— gọi từ code khác nếu cần.
JSON mẫu server (phẳng, đúng JsonUtility)
{
"contentVersion": "12",
"assetsBaseUrl": "https://cdn.example.com/Android"
}
2.5. AddressableManager.cs
- Trước
Addressables.InitializeAsync()awaitGameContentBootstrap.WaitForPreAddressablesSetupIfAnyAsync()để không init Addressables trước khi bootstrap xong HTTP + rewrite (khi gate được tạo).
3. Bảng liên hệ “MD nghiệp vụ ↔ code”
| Nghiệp vụ | Code |
|---|---|
| Kiểm tra / cập nhật catalog | AddressablesCatalogUpdater + gọi trong GameContentBootstrap |
| Đổi CDN runtime | AddressablesRuntimeUrlRewriter + assetsBaseUrl từ API |
| First run / mỗi lần mở + so version (server hoặc hardcode) | GameContentBootstrap + GameContentVersionServerClient + PlayerPrefs |
| Init Addressables sau rewrite | Gate + AddressablesInitService + AddressableManager chờ gate |
| Init tập trung (tránh init sớm) | AddressablesInitService — không gọi Addressables.InitializeAsync() trực tiếp; AUIManager đã chuyển sang service |
4. Checklist cấu hình (team)
- Scene bootstrap có
GameContentBootstrap: tắt_useServerForVersionInfo+ gán hardcode khi test; production bật server +_versionEndpointUrl. _bakedRemoteUrlPrefixForRewritetrùng prefix URL đã bake trong bản build đang chạy (không chỉRemote.LoadPathhiện tại trong Editor). Ví dụ build cũ trỏhttps://prefect-world-asset....wcsapi.com/→ phải điền đúng prefix đó;_hardcodedAssetsBaseUrl/ serverassetsBaseUrl= CDN mới (vdhttps://pw-assets.brewmonster.vn/Android/).- Mọi entry remote cần “tải full” lần đầu / khi đổi version đều có cùng label với
_remoteBulkDownloadLabel(mặc địnhRemoteContent). - Build Addressables đúng platform → upload đúng
ServerData/<Platform>lên host (nếu dùng remote). - Có luồng catalog khi hot-update (bootstrap đã gọi khi version đổi / first run).
- Production dùng HTTPS; mọi chỗ load Addressables dùng
AddressablesInitService(hoặcAddressableManager.WaitUntilInitializedAsync), khôngInitializeAsync().WaitForCompletion()trực tiếp. - Hai scene: index 0 =
GameContentBootstrap, không gắnGameContentBootstraptrênBootstrap.unity(chạy menu Setup Two-Scene Bootstrap). _nextSceneName=Bootstrap;BootstrapSceneController._nextSceneNamevẫn trỏLoginScene(hoặc scene in-game) như cũ.
5. Mobile: SSL CA certificate error / catalog URL CDN cũ
Triệu chứng: CheckForCatalogUpdates / tải catalog_*.hash lỗi ConnectionError : SSL CA certificate error, URL vẫn là host cũ (vd prefect-world-asset....wcsapi.com) dù profile Editor đã đổi sang CDN khác.
Nguyên nhân thường gặp:
- Init sớm: UI (
AUIManager/CECUIManager.Awake) gọiAddressables.InitializeAsync()trướcGameContentBootstrapgắnInternalIdTransformFunc→ request vẫn tới CDN bake trong catalog build. - Thiếu rewrite:
assetsBaseUrlcó nhưng_bakedRemoteUrlPrefixForRewritetrống → không đổi host. - Chứng chỉ CDN: Chuỗi SSL/intermediate trên host đích không tin cậy được trên Android (sửa phía CDN / Let's Encrypt full chain).
Đã xử lý trong code: AddressablesInitService chờ gate bootstrap; AUIManager dùng service thay vì init trực tiếp.
Bạn cần kiểm tra trên scene mobile:
| Field | Gợi ý |
|---|---|
_bakedRemoteUrlPrefixForRewrite |
Prefix y hệt URL trong catalog bản APK (vd https://prefect-world-asset.wcscdn51.v1.wcsapi.com/) |
_hardcodedAssetsBaseUrl |
CDN đích HTTPS hợp lệ (vd https://pw-assets.brewmonster.vn/Android/) |
Log mong đợi trước init: [Cuong] GameContentBootstrap: URL rewrite | from=... → to=...
Các file khác vẫn có thể gọi InitializeAsync().WaitForCompletion() (EC_Game, EC_HPWorkNavigate, …) — nên chuyển dần sang AddressablesInitService.
6. Ghi chú Editor
- Bật
_skipRemoteCallInEditorđể không gọi mạng; dùng_hardcodedContentVersion/_hardcodedAssetsBaseUrlhoặc_editorFakeContentVersion(khi hardcode version trống).
7. Debug log runtime ([Cuong])
Trong Console Unity, lọc [Cuong] để theo dõi bootstrap / tải remote.
Tiến độ tải (% + MB)
AddressablesCatalogUpdater.DownloadDependenciesAsync log mỗi 5% (mặc định) và lúc bắt đầu / kết thúc 100%, kèm dung lượng khi Addressables báo được TotalBytes:
[Cuong] AddressablesCatalogUpdater: Bắt đầu tải dependencies (key=RemoteContent)...
[Cuong] AddressablesCatalogUpdater: 0% (0.0/512.3 MB) — key=RemoteContent
[Cuong] AddressablesCatalogUpdater: 5% (25.6/512.3 MB) — key=RemoteContent
[Cuong] AddressablesCatalogUpdater: 10% (51.2/512.3 MB) — key=RemoteContent
...
[Cuong] AddressablesCatalogUpdater: 100% (512.3/512.3 MB) — key=RemoteContent
[Cuong] AddressablesCatalogUpdater: Tải xong dependencies (key=RemoteContent).
Trước khi bulk download, GameContentBootstrap gọi GetDownloadSizeBytesAsync và log dung lượng cần tải (ước lượng theo catalog + cache):
[Cuong] GameContentBootstrap: Đang tải remote content (label=RemoteContent), dung lượng cần tải ~512.3 MB...
Nếu đã cache đủ: dung lượng cần tải ~0 MB hoặc log “đã có trong cache”.
Đổi bước log %: gọi DownloadDependenciesAsync(key, progressLogStepPercent: 10) (1–100).
Bảng log theo giai đoạn
| Giai đoạn | Ví dụ log |
|---|---|
| Bắt đầu bootstrap | GameContentBootstrap: Bắt đầu bootstrap Addressables... |
| Đang lấy version | Đang lấy contentVersion / assetsBaseUrl... |
| Đang init / catalog | Đang khởi tạo Addressables..., Đang kiểm tra / cập nhật catalog... |
| Trước bulk | dung lượng cần tải ~N MB (label=...) |
| Đang tải bulk | AddressablesCatalogUpdater: N% (x/y MB) — key=... (mỗi ~5%) |
| Xong (không cần tải) | Hoàn tất — không cần tải catalog/bulk (version không đổi). |
| Xong (đã tải hết) | Bootstrap hoàn tất — đã tải xong hết nội dung |
| AddressableManager | Bootstrap gate xong, InitializeAsync xong — sẵn sàng load asset |
Lỗi dùng Debug.LogError cùng tiền tố [Cuong].
Tài liệu mô tả hành vi và cấu hình trong repo; chi tiết API theo đúng phiên bản com.unity.addressables nên đối chiếu Unity Manual: Addressables.