Tổng hợp vấn đề với Unity. Tổng hợp các vấn đề và cách giải quyết gặp phải trong quá trình phát triển game với Unity. Build Problems Không tăng Bundle Version Code khi build Android Bundle Version Code là gì? Bundle Version Code (BVC) là một số nguyên sử dụng để xác định phiên bản của ứng dụng. Số này không hiển thị cho người dùng nhưng rất quan trọng với Google Play Store và thiết bị Android để quản lý việc cập nhật ứng dụng. Khác với Version (version name) là một string để hiển thị cho người, mô tả về phiên bản ứng dụng. Đặc điểm chính Là một số nguyên (0, 1, 2, 101...) được Unity khuyến nghị nên nhỏ hơn 100000 để buildApkPerCpuArchitecture có thể tạo ra version code hợp lệ. Không hiển thị với người dùng. Quan trọng cho việc cập nhật. Vì sao cần tăng Bundle Version Code? Như đã nói ở trên BVC được Google Play Store và Android sử dụng để xác định số phiên bản. Nên không thể cài đè một phiên bản có BVC nhỏ hơn phiên bản hiện tại hoặc trên Google Play Store, Google không chấp nhận việc cập nhật nếu bạn tải lên một phiên bản có BVC nhỏ hơn hoặc bằng phiên bản hiện tại. Quy ước đánh số Bundle Version Code. Dựa theo khuyến nghị của Unity, quy ước Semantic versioning ta có thể chia BVC thành 3 phần mỗi phần tương ứng với 2 chữ số tương ứng với MAJOR.MINOR.PATCH Từ đó ta có công thức như sau bundleVersionCode = MAJOR*10000 + MINOR*100 + PATCH Giải pháp hạn chế quên tăng Bundle Version Code! Viết một script triển khai interface IPreprocessBuildWithReport để tự động tính BVC theo công thức trên. Tạo một của sổ Build Tools mới bằng EditorWindow để hiển thị các thông tin thường xuyên cần chỉnh sửa như Icon, BVC... Hoặc kết hợp cả 2 cách trên. Game Development Problems Các vấn đề gặp phải trong quá trình phát triển game với Unity Các khớp bị cắt khi sử dụng Spine Animation. Vấn đề Lỗi bị viền tại các khớp khi dùng spine animation. Lý do Do config export file không trùng với config trong Unity. Giải pháp Sử dụng color space Gamma. (để trùng với export mặc định của Unity). Đọc thêm tại spine-unity Assets. Không thể using một namespace. Vấn đề Không thể sử dụng một namespace dù đã thêm package chính xác và có thể sử dụng ở các flie khác. Trường hợp cụ thể SoundsGood by MelenitasDev không thể using UnityEngine.AddressableAssets. Lý do Các scripts được biên dịch thành một Assembly riêng biệt. Nếu namespace không được Assembly Definition Files (asmdef) tham chiếu thì sẽ không sử dụng được. Giải pháp Mở file Assembly Definition Files và thêm tham chiếu đến asmdef cần sử dụng (VD: Unity.Addressables…) Copy một List Vấn đề: Trong C# khi tạo một list mới bằng các List list = new(otherList) thì các phần tử của list vẫn được tham chiếu đến các phần tử của otherList Nguyên nhân: Trong C# khi sử dụng lệnh List list = new(otherList) chỉ tạo ra được một shallow copy (copy nông). Các phần tử vẫn được tham chiếu đến một vị trí. Giải pháp Tạo ra một deep copy (copy sâu) Sử dụng vòng for duyệt và tạo mới từng phần tử sau đó thêm vào list mới. Sử dụng Linq để tạo phần từ mới List list = otherList.Select(e => new T()).ToList(); List list = otherList.ConvertAll(e => new T()); Release sai cách khi sử dụng Addressables Vấn đề. Release sai cách khi sử dụng Addressables Case 1: Early-Release. Với những assets cần sử dụng nhiều lần. Việc Release ngay khi load xong khiến cho mỗi lần cần sử dụng. Asset sẽ phải load từ ổ cứng lên. public async UniTask CreateCharacter(CharacterSheet.Row characterData) { var handle = Addressables.LoadAssetAsync(characterData.PrefabAddr); await handle.Task; GameObject characterObj = resolver.Instantiate(handle.Result); handle.Release(); return characterObj.GetComponent(); } Case 2: Cache chưa chính xác. Trong trường hơp này handle.Result đã được cache vào Dictionary nhưng handle lại bị release ngay sau đó. Tiềm ẩn vấn đề Value này sẽ được tham chiếu đến một "zombie object" gây ra lỗi NullReferenceException hoặc ArgumentNullException private Dictionary projectileCaches = new(); public async UniTask CreateProjectile(ProjectileSheet.Row projectileData) { if (!projectileCaches.TryGetValue(projectileData.Id, out var projectilePrefab)) { var handle = Addressables.LoadAssetAsync(projectileData.PrefabAddr); await handle.Task; projectileCaches.TryAdd(projectileData.Id, handle.Result); projectilePrefab = handle.Result; handle.Release(); DebugExtension.Log("Load from disk!!!", Color.red); } DebugExtension.Log("Load from cache!!!", Color.green); GameObject projectileObj = resolver.Instantiate(projectilePrefab); return projectileObj.GetComponent(); } Cơ chế đếm tham chiếu của Addressables Addressables không hoạt động theo kiểu bật/tắt đơn giản mà sử dụng một cơ chế thông minh gọi là Reference Counting. Mỗi khi asset được load lên RAM bộ đếm tham chiếu sẽ tăng lên. Và ngược lại mỗi khi release bộ đếm tham chiếu sẽ giảm đi. Asset sẽ chỉ được unload khỏi ram khi ref-count trở về 0. Ví dụ A.cs gọi handleA = LoadAssetAsync(mySprite) mySprite được load vào RAM. Ref-Count = 1 B.cs gọi handleA = LoadAssetAsync(mySprite) mySprite được lấy từ RAM Ref-Count = 2 A.cs gọi handleA.Release() Ref-Count = 1 → mySprite vẫn còn trên RAM Lúc này, nếu có C.cs gọi handleC = LoadAssetAsync(mySprite) thì mySprite vẫn được lấy từ RAM. Ref-Count = 2 Hoặc nếu B.cs gọi handleB.Release() thì Ref-Count = 0. mySprite bị unload khỏi RAM. Tiếp theo, nếu có bất kỳ scripts nào gọi gọi LoadAssetAsync(mySprite) thì mySprite sẽ được tải lại từ ổ cứng. Giải pháp Với những object chỉ cần sử dụng một lần. Hoàn toàn có thể release ngay sau khi load. Với những object sử dụng nhiều lần. Có thể sử dụng Addressables.InstantiateAsync() hoặc cache handle để có thể release khi không còn sử dụng đến nữa. // Cache handles public class ProjectileFactory { private Dictionary> loadedPrefabs = new(); public async UniTask PreloadProjectiles(List allProjectileData) { foreach (var data in allProjectileData) { if (!loadedPrefabs.ContainsKey(data.Id)) { var handle = Addressables.LoadAssetAsync(data.PrefabAddr); loadedPrefabs.Add(data.Id, handle); } } await UniTask.WhenAll(loadedPrefabs.Values.Select(h => h.AsUniTask())); } public IProjectile CreateProjectile(string projectileId) { if (loadedPrefabs.TryGetValue(projectileId, out var handle)) { GameObject projectileObj = resolver.Instantiate(handle.Result); return projectileObj.GetComponent(); } return null; } public void UnloadAll() { foreach(var handle in loadedPrefabs.Values) Addressables.Release(handle); loadedPrefabs.Clear(); } } Release Problems Vấn đề khi build release dự án Unity Build iOS thành công nhưng không có file .xcworkspace Vấn đề Khi tích hợp đầy đủ các SDK, cocapods, build iOS trên Unity báo success nhưng không có file .xcworkspace để archive Nguyên nhân Do lỗi các mạng quảng cáo không tương thích với phiên bản iOS tối thiểu Do các mạng quả cáo không tương thích với Mediation SDK ... Giải pháp Sử dụng terminal cd tới folder build iOS (VD: cd /Users/admin/Unity/Gang2022/Builds/558/iOS) Chạy lệnh pod install để build lại file .xcworkspace Đọc, kiểm tra lỗi và tiếp tục xử lý Lỗi AppLovinMediationGoogleAdapter không tương thích với Google-Mobile-Ads-SDK => Sửa phiên bản Google-Mobile-Ads-SDK trong file Podfile [!] CocoaPods could not find compatible versions for pod "Google-Mobile-Ads-SDK": In Podfile: AppLovinMediationGoogleAdapter (= 12.5.0.0) was resolved to 12.5.0.0, which depends on Google-Mobile-Ads-SDK (= 12.5.0) Google-Mobile-Ads-SDK (~> 11.13.0) Specs satisfying the `Google-Mobile-Ads-SDK (~> 11.13.0), Google-Mobile-Ads-SDK (= 12.5.0)` dependency were found, but they required a higher minimum deployment target. Lỗi AppLovinMediationMyTargetAdapter yêu cầu phiên bản iOS tối thiểu cao hơn => trong Podfile sửa dòng platform :ios, 'iOS_version' hoặc thay đổi Target minimum iOS Version trong Player Settings [!] CocoaPods could not find compatible versions for pod "AppLovinMediationMyTargetAdapter":   In Podfile:     AppLovinMediationMyTargetAdapter (= 5.30.0.0) Specs satisfying the `AppLovinMediationMyTargetAdapter (= 5.30.0.0)` dependency were found, but they required a higher minimum deployment target. Nếu không còn lỗi và đã có file .xcworkspace thì có thể thực hiện archive trên file .xcworkspace này Lưu ý Dùng lệnh pod install --repo-update để install đồng thời update tất cả các pod trong Podfile Dọn dẹp cache pod cache clean --all rm -rf ~/Library/Caches/CocoaPods rm -rf Pods rm -f Podfile.lock Tích hợp FacebookSDK - Open SSL not found Vấn đề Nguyên nhân Do chưa cài đặt môi trường SSL và Java Giải pháp OpenSSL not found Download and install OpenSSL. Win32/Win64 OpenSSL Installer for Windows - Shining Light Productions Win32 OpenSSL v#.#.# (not Light) OR Win64 OpenSSL v#.#.# (not Light) Add the OpenSSL directory to your path. Go to:  Control Panel →  System →  Advanced system settings →  Environment Variables Select the Variable  Path in the “System variables” window and click Edit. Add the path to your OpenSSL bin folder to the end of the “Variable value” text. e.g. I added  ;C:\Program Files\OpenSSL-Win64\bin to the end of the value text. → Take note, do not forget to add semi-colon  ; before the  C:/ Restart computer. Keytool not found Lỗi Undefined symbol khi build iOS Vấn đề Khi build iOS vẫn thành công. Có file .xcworkspace nhưng khi Archive lại gặp rất nhiều lỗi Undefined symbol như hình dưới. Nguyên nhân Do các thư viện GoogleMobileAds, YandexMobileAds.... chưa được nhúng vào project. Giải pháp Thêm thủ công vào .xcworkspace Trong của sổ làm việc của XCode chọn project Unity-iPhone > Targets Unity-iPhone > General > Frameworks, Libraries, and Embedded Content Nhấn + để thêm các thư viện còn thiếu. Ở trường hợp trên là GoogleMobileAds.xcframework và YandexMobileAds.xcframework. Cách khác (không biết gọi là gì =))))) Với XCode 26.5 khi thêm thủ công file .xcframework có thể sẽ gặp lỗi sau Lỗi này xảy ra với file YandexMobileAds.xcframework được tải trên internet. Với trường hợp này chúng ta sẽ không thêm thủ công GoogleMobileAds.xcframework và YandexMobileAds.xcframework nữa. Giữ Frameworks, Libraries, and Embedded Content như ban đầu. Trên thanh menu bên trái của Xcode, mở folder Project > Unity-iPhone > Frameworks. - Nếu bạn thấy GoogleMobileAds và YandexMobileAds màu đỏ như hình thì chuột phải > Delete > Remove Reference - Nếu không thì thôi :))))) Giờ sang phần Target > Build Settings > tìm "Framework Search Paths" > Nháy đúp vào và thêm 2 dòng lệnh sau - $(SRCROOT)/Pods/YandexMobileAds - $(SRCROOT)/Pods/Google-Mobile-Ads-SDK Đây là biến môi trường tự động trỏ đúng vào thư mục project của bạn. Nhìn sang bên cạnh nếu thấy non-recursive thì nhớ chuyển nó thành recursive. Để báo cho XCode biết cần phải đi tìm file .xcframework trong các thư mục con. Tiếp tục, vẫn ở Build Settings, tìm "Other Linker Flags", nháy đúp vào và thêm lần lượt 4 dòng sau vào cuối. -framework YandexMobileAds -framework GoogleMobileAds Xong rồi, giờ build thôi :> À quên, nhớ Clean Build Folder trước khi build nhé. Lưu ý Framework được thêm vào nên có màu xanh (hoặc vàng) nhạt. Nếu màu đậm khả năng cao sẽ gặp lỗi Nếu gặp lỗi như trên có thể chọn framework từ một dự án khác đã Archive thành công để thêm vào. Nếu không có dự án cũ thì có thể tìm và tải file .xcframework trừ internet. Với GoogleMobileAds.xcframework có thể tham khảo link này hoặc search google với từ khóa GoogleMobileAds.xcframework. Với YandexMobileAds.xcframework có thể tham khảo Link này > tìm phiên bản đang sử dụng > tìm field source.http > sử dụng link này để tải file zip có YandexMobileAds.xcframework. search google với từ khóa YandexMobileAds.xcframework. Tích hợp FacebookSDK - Lỗi khi Archive trong XCode Vấn đề Dự án Unity có tích hợp FacebookSDK. Build iOS thành công nhưng khi Archive trong XCode lại gặp lỗi như hình. Nguyên nhân Do xung đột phiên bản FacebookSDK trong Unity và trong XCode Hoặc, do lỗi của phiên bản FacebookSDK Giải pháp Kiểm tra xem phiên bản FacebookSDK trong Unity và trong Podfile (cùng thư mục với file .xcworkspace) có trùng nhau chưa. Nếu chưa hãy update lại FacebookSDK trong Unity rồi build lại. Nếu đã trùng nhau nhưng vẫn gặp lỗi. Vấn đề khẳ năng cao là do phiên bản FacebookSDK. Tại đây có 2 cách giải quyết: Hạ xuống phiên bản FacebookSDK ổn định hơn. (Trong trường hợp này là 17.0.1, phiên bản gặp lỗi là 18.0.0) Nếu tính năng bị lỗi là một tính năng mà bạn không sử dụng đến hãy xóa hoặc comment dòng code lỗi đó. Tìm đến dòng code báo lỗi và comment lại trong XCode (vẫn phải thực hiện lại trong các lần build sau, trừ khi update FacebookSDK không gặp lỗi) Trong Unity, FacebookSDK > SDK > Editor > iOS > Swift > Comment các dòng code báo lỗi tương tự như trên (với cách này thì chỉ cần làm một lần)