استفاده از Asset Bundle برای نسخه Google Instant

برای انتشار بازی برای گوگل پلی اینستنت (به اختصار اینستنت) نیاز است که حجم بازی کمتر از 20 مگ (در حال حاضر) باشد. با توجه به ابعاد بازی با فرض بهینه‌سازی‌ اَسِت‌ها (assets) و حذف موارد غیرضروری، ممکن است باز هم رسیدن به این حجم ممکن نباشد. در اینجاست که چاره‌ای نیست به جز استفاده از Asset Bundle.در این مقاله سعی می‌کنیم راهی برای استفاده از این ابزار برای کم کردن حجم بازی معرفی و پیچیدگی‌های موجود در این مسیر را بررسی کنیم.۰- اَسِت‌ها و خروجی نهاییابتدا در نظر داشته باشید که صحنه‌هایی (Scenes) که در Build Settings مشخص شده باشند با تمام اَسِت‌هایی که در این صحنه‌ها به آن‌ها رفرنس داده شده باشد در بیلد نهایی خواهند آمد.همچنین تمام اِسِت‌هایی که در فولدر Resources (هر فولدری با این اسم زیر پوشه Assets با هر عمقی) و فولدر StreamingAssets و همچنین اَسِت‌هایی که در این جا به آن‌ها رفرنس داده شده باشد در بیلد نهایی خواهند آمد.همچنین تمام کد‌هایی که در فولدر Editor نباشند و زیر پوشه Assets باشند یا Assembly Definition داشته باشند به صورت پیش‌فرض در بیلد نهایی خواهند آمد.مواردی که در بیلد نهایی شامل می‌شوند را می‌توانید با جزئیات از طریق پنجره Console با کلیک روی سه نقطه بالا سمت راست و باز کردن ادیتور لاگ (Open Editor Log) ببینید. اسکرول کرده تا لاگ‌های مربوط به آخرین بیلد را مشاهده کنید.«نمونه لاگ و حجم قسمت‌های مختلف بدون فشرده‌سازی در خروجی نهایی»۱- استفاده از Asset Bundleدر این روش می‌توان اَسِت‌هایی (شامل پریفب، موزیک، اسپرایت و ...) و همچنین صحنه‌ها (در هر باندل یا اَسِت یا صحنه) را باندل کرد تا بعدا در بازی آن‌ها را دانلود کرده و استفاده کنید. این باندل‌ها را می‌توانید به روش‌های متفاوت سِرو کنید. در این مقاله از APIهای خود گوگل برای سِرو کردن این باندل‌ها استفاده می‌کنیم تا هم اطمینان بالاتری داشته باشیم هم دردسرهای راه‌اندازی سرور را نداشته باشیم. اگر از یونیتی 2019.4 و بالاتر و همچنین Addressables استفاده می‌کنید، می‌توانید از امکان Addressables برای دانلود کردن اَسِت‌ها بهره ببرید. آموزش‌های این روش به همراه مثال در سایت این پلاگین موجود است.برای این منظور پکیج Google Asset Delivery را دانلود کرده و به پروژه اضافه می‌کنیم. دقت کنید در هنگام نوشتن این مقاله آخرین ورژن (1.8.2) نیازمند یونیتی 2023 است که همچنان در حالت بتا قرار دارد!!!!!از این صفحه می‌توانید نسخه‌های قبل این پکیج رو پیدا کنید. در این مقاله از نسخه 1.7.0 استفاده می‌کنیم.۱.۱- باندل کردن اَسِت‌ها و صحنه‌هابا توجه به روندی که برای ساخت بازی طی کردید یکی از دو راه باندل کردن اَسِت‌ها یا باندل کردن صحنه یا هردو رو می‌تونید استفاده کنید.اگر بیشتر محتوی را در فولدر Resources قرار دادید روش اول و اگر در صحنه‌ها مستقیما از اَسِت‌ها استفاده کردید روش دوم را بروید. اگر بخش قابل توجهی در فولدر Resources و هم در صحنه دارید، باید حداقل دو باندل مختلف یکی برای هر کدوم بسازید.برای باندل کردن اَسِت‌ها از پایین پنجره Inspector روی Asset Labels کلیک کنید تا پنجره preview باز شود (اگر آلردی باز نیست) سپس فیلد AssetBundle را برابر اسم مورد نظر قرار بدید. اگر پکیج گوگل را اضافه کرده باشید حداقل دوتا باندل پیش‌فرض اضافه شده است. از اینا استفاده نکنید زیرا اَسِت‌های مثال‌ خود گوگل هم وارد بیلد نهایی می‌شود. در این مقاله از "instantbundle" استفاده می‌کنیم.«انتخاب اسم instantbundle برای یک پریفب»برای صحنه‌ها نیز به همین روش یک باندل مناسب انتخاب کنید. دقت کنید نمی‌توانید از یک اسم برای اَسِت‌ها و صحنه‌ها استفاده کنید. می‌توانید چند باندل مختلف ساخته و مثلا در صورتی که بازی بسیار حجیم است، مواردی که پلیر در ابتدای گیم به آن‌ها نیاز پیدا خواهد کرد را زودتر دانلود کنید.۱.۲- ساخت باندلبرای ساخت باندل (‌Build asset bundle) چندین روش مختلف وجود دارد. در این مقاله از تیکه کد زیر برای اضافه کردن گزینه ساخت باندل به منوی Assets استفاده می‌کنیم. https://gist.github.com/vbashiri/de62a8ce101cb8effed135e8fc60084f تیکه کد بالا را با اسم BuildAssetBundles.cs زیر پوشه‌ای با اسم Editor (زیر پوشه Assets باشد مهم نیست در چه عمقی، مثلا Assets/Scripts/Editor/BuildAssetBundles.cs مناسب است) ذخیره کنید. در یونیتی از تب بالا Assets را باز کنید و دکمه Build AssetBundles را بزنید.«گزینه Build AssetBundles در تب Assetsکمی صبر کنید تا است‌باندل‌ها بیلد شوند. بعد از تمام شدن فرایند بیلد، باید پوشه AssetBundles زیر پوشه Assets ساخته شده باشد که باندل‌هایی که مشخص کرده بودید در این پوشه ساخته شده‌اند. حال از تب بالا منوی گوگل را انتخاب کنید. پنجره‌ای مشابه پنجره‌ی پایین باید باز شود.«پنجره Google Asset Deliveryحال به روی دکمه Add Folder کلیک کرده و فولدر ساخته شده در مرحله قبل را انتخاب کنید. لیست باندل‌های ساخته شده با Dependencyها (اگر چند باندل داشته باشید که به هم وابسته باشند)، خطاها و حجمشون اینجا قابل مشاهده خواهند بود. Delivery Mode این پکیج‌ها به صورت پیشفرض روی Do Not Package است که وارد بیلد نهایی نمی‌شوند. پکیج‌هایی که می‌خواهید استفاده کنید را روی On Demand بگذارید.دقت کنید برای نسخه اینستنت فقط از حالت On Demand می‌توانید استفاده کنید. اگر برای اهداف دیگری از Asset Bundle استفاده می‌کنید می‌توانید از گزینه‌های Install Time و Fast Follow استفاده کنید تا به محض نصب بازی پکیج‌ها به صورت خودکار دانلود شوند.همچنین می‌توانید با نصب پکیج AssetBundle Browser (با استفاده از Package Manager) نیز لیست باندل‌ها را با اَسِت‌ها و صحنه‌های داخلشان مشاهده کنید، بیلد بگیرید یا کمی ادیت کنید.«پنجره AssetBundle Browser که لیست باندل‌ها و اَسِت‌های داخلش را نشان می‌دهد. در این قسمت مواردی که مستقیما باندل نشده‌اند اما نیازمندی بوده‌اند نیز قابل مشاهده است.»۱.۳- استفاده از باندل‌هاهمانطور که در بخش 1.1 اشاره شد دو روش برای استفاده از باندل‌ها وجود دارد (احتمالا بیشتر از دو روش ولی ما اینجا به همین دو روش می‌پردازیم D:).۱.۳.۱- مشابه فولدر Resources:مشابه استفاده از فولدر ریسورسز برای لود کردن اَسِت‌ها می‌توان با دانلود باندل، این اَسِت‌ها رو لود کرد. برای این منظور ابتدا یک درخواست برای اَسِت‌باندل مذکور ایجاد و رفرنس آن‌ را نگه می‌داریم.PlayAssetBundleRequest bundleRequest = PlayAssetDelivery.RetrieveAssetBundleAsync(assetBundleName);از این درخواست می‌توان برای پیگیری وضعیت درخواست استفاده کرد:bundleRequest.IsDone //To see if request is done :D bundleRequest.DownloadProgress //To see how much is downloaded (between 0 and 1). This can be used to show a progress bar to player. Must be checked in a loop, of course.بعد از تمام شدن دانلود باندل (باندل به شکل خودکار کش شده و دفعات بعد به سرعت deliver می‌شود) باندل را از رفرنس درخواست بگیرید و جایی نگه‌دارید:if (bundleRequest.Error == AssetDeliveryErrorCode.NoError) { Debug.Log(&quotAsset bundle Downloaded with no error...&quot); instantBundle = bundleRequest.AssetBundle; }تیکه کد زیر، یک روند پیشنهادی برای دانلود و نشان دادن نوار پیشرفت به کاربر است (تیکه کد بخشی از یک سیستم بزرگ‌تر است و برای آموزش کوچک شده است، بنابراین ممکن است به همین شکل کار نکند. در صورت بروز مشکل در قسمت کامنت، به شکل پیام، با دود یا هر روشی به من اطلاع دهید تا اصلاح کنم. D:): https://gist.github.com/vbashiri/266bc7b7e7d41723fec13b1eff68ccf5 بعد از دانلود کردن باندل، شبیه ریسورسز می‌توانیم اَسِت‌ها را لود کرده و استفاده کنیم.instantBundle.LoadAsset<GameObject>(&quotassets/address/to/asset.prefab&quot);تفاوت اصلی در نوع ذخیره سازی در باندل است. اَسِت‌ها در باندل به شکل مسطح ذخیره می‌شوند و فولدربندی نداریم، اگرچه آدرس کامل آن‌ها به صورت lowercase برای هر فایل ذخیره می‌شود. در AssetBundle Browser تب اینسپکت، می‌توانید آدرس تمام فایل‌های داخل را ببینید. همچنین برای لود کردنشان (در صورت استفاده از آدرس کامل) باید پسوند فایل رو هم اضافه کنید.«فایل‌های داخل باندل و آدرس کامل آن‌ها»بعد از ساخت اَسِت‌باندل اگر این فایل‌ها را پاک کنید، در پنجره اشاره شده، فایل‌ها قابل مشاهده نخواهد بود اما همچنان در باندل وجود دارند. صرفا این ابزار توانایی نشان دادن آن‌ها را ندارد. بنابراین توصیه می‌شود از این ابزار برای دستکاری یا ساخت باندل‌ها استفاده نکنید.۱.۳.۲- باندل کردن Scene:در مواردی که اَسِت‌ها مستقیما داخل صحنه استفاده شده‌اند یا به آن‌ها در صحنه رفرنس داده شده است برای کم کردن حجم بازی نیاز است کل صحنه را باندل کرده و از خروجی حذف کنیم. در این روش مانند روش قبل باندل را دانلود کرده و سپس لیست صحنه‌های داخل باندل بگیرید.sceneBundle = bundleRequest.AssetBundle;string[] scenePath = sceneBundle.GetAllScenePaths();با استفاده از اسم صحنه‌ها می‌توانید صحنه مورد نظر را لود کنید.SceneManager.LoadScene(scenePath[0]);واضح است که اگر فقط یک صحنه در بازی دارید، نیاز مند ساخت یک صحنه جدید برای دانلود باندل و خوش‌آمد‌گویی به کاربر هستید. فراموش نکنید که از قسمت Build Settings صحنه‌ی اصلی را از خروجی حذف کنید وگرنه کاهش حجمی صورت نخواهد گرفت.۱.۴- مشکلات رایجقبل از اینکه بیلد گرفتن پروژه را شروع کنیم نیاز است چند مشکل را رفع کنیم. در ادامه مشکلاتی که احتمالا با آن برخورد خواهید کرد لیست شده است. قابل ذکر است که می‌توانید مستقیم به بخش ۱.۵ بروید و نسخه اینستنت را بیلد بگیرید و با بروز مشکلات به این بخش مراجعه کرده و آن‌ها را رفع کنید. همچنین در صورتی که مشکلی ذکر نشده بود یا در روند آپدیت پکیج‌ها تغییر کرده یا رفع شده بود اطلاع دهید تا مقاله نیز آپدیت شود.توجه داشته باشید استفاده از باندل مانند گرفتن اسنپ‌شات از اَسِت‌هاست. اگر این اَسِت‌ها در فولدر Resources باشند، در صحنه به آن‌ها رفرنس شده باشد یا صحنه در قسمت Build Settings همچنان وارد خروجی شده باشند باز‌ هم در حجم نهایی موثر خواهند بود. از آن مهم‌تر بعد از تغییر این اَسِت‌ها و صحنه‌ها اسنپ‌شات آپدیت نخواهد شد و باید دوباره باندل را بیلد بگیرید.۱.۴.۱- خطای java.lang.ClassNotFoundException برای کلاس AssetPackManagerFactory هنگام درخواست دانلود باندل: برای این رفع این مورد فایل asset_delivery.txt را باز کنید فایل در پوشه‌ی Assets/GooglePlayPlugins/com.google.play.assetdelivery/Proguard قرار دارد.رول مربوط به AssetPackManagerFactory را پیدا کرده و با رول کلی‌تر زیر جایگزین کنید.رول دیفالت:-keep class com.google.android.play.core.assetpacks.AssetPackManagerFactory { <init>(); public static com.google.android.play.core.assetpacks.AssetPackManager getInstance(android.content.Context); }رول جایگزین:-keep class com.google.android.play.core.assetpacks.** {*;}در صورتی که پروژه را مینیفای کنید (که پیشنهاد می‌شود بکنید، بسته به مقدار کد‌های بازی تا 0.5 مگ می‌تواند از حجم پروژه بکاهد) باید این رول را به فایل پروگارد یونیتی نیز اضافه کنید. برای مینیفای کردن در قسمت Project Settings تب Player قسمت Publishing Settings را باز کنید (اگر این قسمت وجود ندارد ممکن است پلتفرم هدف پروژه‌ی شما انروید نباشد.) و در انتها زیر قسمت Minify هردوی Release و Debug را روی حالت Proguard قرار دهید. سپس تیک Custom Proguard File را اگر نزدید بزنید تا یک فایل زیر پوشه Assets/Plugins با اسم proguard-user.txt برای شما ساخته شود.«تنظیمات Publishing Settings در تب Player در پنجره Project Settingsحال رول‌های زیر را به آن اضافه کنید.-keep class com.google.android.play.core.assetpacks.** {*;} -keep class com.google.android.play.core.tasks.** {*;}۱.۴.۲- مشکل گیر کردن وضعیت دانلود در حالت pending:در مواردی دیده شده که وضعیت دانلود باندل در حالت pending گیر می‌کند، هرچند بستن بازی به شکل کامل و باز کردن آن معمولا این مشکل را رفع می‌کند اما تجربه خوبی برای بازیکن به همراه ندارد. برای رفع این مورد بهتر است شرطی برای حالت طولانی شدن وضعیت pending در نظر بگیرید و در صورت رخداد آن درخواست را کنسل کرده و دوباره تلاش کنید.این مورد به شکل کامل در این قسمت توضیح داده شده است ولی سال‌هاست به شکل قطعی حل نشده است (گوگل داری با خودت چکار می‌کنی؟ :))))برای کنسل کردن در‌خواست می‌توانید از قطعه کد زیر در لوپ تکرار پیشنهادی در بخش ۱.۳.۱ استفاده کنید. قبل از ورود به حلقه تکرار، مقدار startTime را سِت کنید و همچنین ۲۰ ثانیه به عنوان مقدار مناسب «صبر» در نظر گرفته شده است:if ((Time.realtimeSinceStartup - startTime) > 20 && bundleRequest.Status == AssetDeliveryStatus.Pending) { bundleRequest.AttemptCancel(); yield return new WaitUntil(() => bundleRequest.Error == AssetDeliveryErrorCode.Canceled); startTime = Time.realtimeSinceStartup; bundleRequest = PlayAssetDelivery.RetrieveAssetBundleAsync(assetBundleName); }۱.۴.۳- خطای indirect_reference_table.cc:65 JNI ERROR (app bug): accessed stale Local 0x1 (index 0 in a table of size 0)این خطا به این معنی است که به قسمتی از حافظه دسترسی پیدا می‌کنید که ممکن است دیگر در دسترس نباشد. با توجه به خطا، اتفاقات زیاد و متفاوتی ممکن است منجر به این پیغام شود (برای مطالعه بیشتر). در مواقعی که از باندل کردن صحنه (در تئوری در روش باندل کردن اَسِت‌ها هم می‌تواند رخ دهد!) استفاده می‌کنید و هنگام لود کردن صحنه از باندل با این پیغام مواجه می‌شوید، به احتمال زیاد قسمتی از برنامه توسط یونیتی با این فرض که نیازی به آن نیست حذف شده است و حالا سعی می‌کنید به آن دسترسی پیدا کنید. این مورد اگر در قسمت Project Settings تب Player قسمت Strip Engine Code را فعال کنید حتی بیشتر هم ممکن است رخ بدهد (پیشنهاد می‌شود این مورد را نیز فعال کنید، بسته به پکیج‌ها و لایبرری‌های استفاده شده و میزان کد‌های بازی تا 0.5 مگ ممکن است از حجم پروژه کم کند).«تنظیمات Strip Engine Code. پیشنهاد می‌شود در صورتی که حجم قابل توجهی لایبرری و کد در پروژه دارید سطح stripping را بالاترین سطح ممکن قرار دهید و به صورت دستی حذف کردن کد‌ها را مدیریت کنید.» برای رفع این مشکل نیاز است به موتور بازی‌سازی به طریقی بفهمانیم مواردی که بیلد شامل نمی‌شوند اما در باندل‌ها قرار است استفاده شوند واقعا نیاز است (برای ما واضحه که نیازه ولی متاسفانه یونیتی خیلی درکش پایینه). دقت کنید با بالا بردن سطح stripping میزان کدهایی که حذف می‌شوند بیشتر است و طبعا شانس رخداد این خطا بالا می‌رود! برای مدیریت تابع‌ها می‌توانید از انوتیشن [preserve] برای نگه‌داشتن کد‌ها استفاده کنید. در این صفحه از مستندات یونیتی میزان حذف برای سطوح مختلف لخت کردن! (stripping) و همچنین انوتیشن‌های مختلف آمده است.نکته مهم: حتی با غیرفعال کردن این گزینه همچنان پکیج‌های خود یونیتی و مهمونی سوم :)) (Third party) مانند Occlusion Culling، NavMesh و Cinemachine ممکن است حذف شوند. برای رفع این مشکل بهتر است این موارد رو در صحنه‌ی خوش‌آمد‌گویی (صحنه‌ی کوچکی که در بیلد شامل می‌شود) نیز استفاده کنید. دقت کنید که این موارد در صورت Bake شدن ممکن است مقدار زیادی داده با خود به همراه بیاورند. این مقدار معمولا در لاگ بیلد زیرمجموعه Level قرار می‌گیرد.۱.۵- بیلد نسخه اینستنتدر قدم اول اگر هنوز پکیج google instant را نصب نکرده‌اید، دانلود و نصب کنید. سپس از تب بالا گوگل را باز کرده و از قسمت اینتسنت Build settings را انتخاب کنید.«Play Instant build settings»در پنجره باز شده، Android Build Type را روی Instant قرار دهید و گزینه Full "Instant play" game را غیرفعال کنید. با اینکه هدف فول اینستنت پلی است، این گزینه با اَسِت‌باندل کار نمی‌کند و نیاز است برای این مورد به شکل پر کردن فرم درخواست دهیم.«کانفلیکت فول اینستنت پلی با اَسِت‌باندل»بعد از تنظیم Build Settings از همان منوی گوگل بیلد را زده، فولدر مناسب را انتخاب کرده و اجازه دهید پروژه ساخته شود.«ساخت پروژه اینستنت»خروجی باید یک فایل .aab خلاصه (Android App Bundle) باشد. دقت کنید که حجم این فایل می‌تواند بیشتر از مینمم تعیین شده توسط گوگل باشد. برای به دست آوردن حجم خروجی نهایی فایل را با یکی از ابزارهای فشرده‌سازی (مثلا Winrar) باز کنید، فولدر base خروجی نهایی و مهم برای گوگل است. در همین فایل باید یک فولدر با اسم باندل‌ ساخته شده توسط شما باشد.فایل aab نهاییتبریک میگم، این فایل، خروجی نهایی شماست و برای آپلود در گوگل پلی قابل استفاده است.۱.۶- تست نسخه اینستنتبرای تست نهایی، بهتر است این نسخه را در گوگل پلی به شکل اینترنال تست آپلود کنید و به عنوان تستر بازی را باز کرده و از کارکرد درست آن اطمینان حاصل کنید. اما با توجه به زمان‌بر بودن این فرایند بهتر است آن را به شکل لوکال تست کنید. برای این منظور ابتدا Bundle Tool را دانلود کرده و نصب کنید. اگر گیت را نصب نکرده‌اید (شرم بر شما!)، آن را هم از این آدرس دانلود کرده و نصب کنید (با فرض اینکه روی ویندوز هستید به گیت‌بش داریم. این مراحل را می‌توانید با Windows cmd یا Powershell نیز انجام دهید).حال با استفاده از باندل‌تول از فایل aab به دست آمده در مرحله قبل، فایل apks را با فلگ--local-testing=trueتولید کرده و بعد از وصل کردن گوشی به کامپیوتر و فعال کردن حالت دولوپر آن را نصب کنید. قطعه کد زیر به عنوان آرگومان آدرس باندل‌تول، کلید Keystore، اسم استفاده شده برای امضای کلید Keystore alias و همچنین فایل aab را بدون پسوند گرفته و خروجی apks را تولید می‌کند. https://gist.github.com/vbashiri/d0faf07421d98b2f40632022270e3f62 می‌توانید دستورات را خط به خط در گیت‌بش (برای باز کردن گیت‌بش راست کلیک کرده و Git Bash Here را انتخاب کنید) اجرا کنید یا فایل را ذخیره کرده و با دستور زیر فایل apks را خروجی گرفته و نصب کنید. به جای براکت‌ها از معادل آنها استفاده کنید../BuildApksAndInstall.sh [Bunde Tool Address] [Keystore Address] [Keystore Alias] [Keystore password] [Path to my awesome game]نکته مهم: اگر از قبل همین نسخه از بازی را به عنوان اینتسنت روی گوشی نصب کرده باشید، قادر به دانلود باندل به شکل لوکال نخواهید بود و با خطای زیر مواجه می‌شوید. Error Unity Failed to retrieve asset pack: Local testing directory '/storage/emulated/0/Android/data/com.funtory.monsterpet/files/local_testing' not found.دستور بالا بازی را به شکل فایل استاندارد (نه اینستنت) روی گوشی نصب می‌کند تا بتوانید باندل را تست کنید.