۱۲ + ۱ نکته برای بهینهسازی بازیهای موبایلی ۲ بعدی در یونیتی
آیفون، آیپد و گجتهای اندرویدی به نسبت کنسول و کامپیوترهای شخصی، سخت افزار ضعیفتری دارند، در نتیجه برای ساخت بازی برای آنها باید برنامهنویس قویتری شوید. هرچند که بهینهسازی بازیتان نباید در ابتدای توسعهی آن انجام شود، اما در اینجا شما با رویههایی آشنا خواهید شد که با پیروی از آنها از تکرارِ یک اشتباه بطور مکرر، جلوگیری خواهید کرد.
بهینهسازی بازی نیمی تجربه و نیمی هنر است. شما باید میانبرها، روشهای متفاوت ایمپورت کردن، رفتار موتور یونیتی و خطهای کدتان را بشناسید. مطمئنا هنگامی که بازیتان روی آیفون قدیمی یا گوشی اندرویدیتان خیلی آهسته اجرا شد، ایدهها و نکتههای این مقاله را بیش از پیش کاربردی و با ارزش خواهید یافت.
نکته شماره ۰: موبایلِ تستِ بازیتان را هوشمندانه انتخاب کنید
هرچه دستگاه کندتر باشد، بهتر است. البته در این کار افراط نکنید زیرا یک گوشی بسیار کند، سرعت شما را خیلی کم خواهد کرد و باعث میشود از ابتدای کار شروع به بهینهسازی کنید که کاری اضافی و غیرضروری است. گوشیهای میانرده گزینههای مناسبی هستند.
بهینهسازی در حجم بازی
گاهی اوقات بازی شما بدون هیچ دلیلی فضای بسیار زیادی را اشغال میکند. حجم بازیتان از اهمیت بسیار بالایی برخوردار است و باید همواره مراقب آن باشید، زیرا این موضوع روی تعداد افرادی که بازیتان را دانلود میکنند تاثیر مستقیم دارد. علاوه بر این، به خاطر داشته باشید که وقتی کاربری نیاز دارد نرمافزاری را حذف کند تا فضای بیشتری برای گوشی خود خالی کند، ابتدا نرم افزارهای با حجم بالا هستند که قربانی میشوند!
نکته شماره ۱: تکسچرها و فایلهای صوتی را فشرده کنید
از بازیتان خروجی بگیرید و سپس درون Editor.log به دنبال فایلهایی بگردید که بیشترین فضا را در بازیتان اشغال کردهاند. اکثر اوقات تکسچرها و فایلهای صوتی حجم بیشتری دارند. برای اینکه تکسچرها بصورت فشرده و کم حجم در بازی مورد استفاده قرار گیرند باید رزولوشن آنها توان دو باشد، مثلا ۱۰۲۴x۲۰۴۸ گزینهی مناسبی است. و ترجیحا این ابعاد را بیشتر از ۲۰۴۸ در نظر نگیرید. همچنین فایلهای صوتی نیز باید همواره حالت فشردهسازیشان (Compression) فعال باشد مگر اینکه فایل صوتی خیلی خیلی کوتاه باشد و فشردهسازی کیفیتش را مختل کند. (که خیلی بعید است).
نکته شماره ۲: اسپرایتها را بستهبندی کنید (Pack)
همواره باید اسپرایتهای UI و بازی را بستهبندی کنید. از ورژن یونیتی ۵ به بعد کاربران امکان استفاده رایگان از Sprite Packer درون موتور یونیتی را دارند. علاوه بر این ابزارهای دیگری مانند NGUI نیز در این زمینه بسیار کارآمد و محبوب هستند. به کمک ابزار Sprite Packer میتوانید به راحتی محدودیتِ اسپرایتها با رزولوشنِ توان دو و ساخت Atlas که گاها از سمت آرتیست ایجاد میشود را از بین ببرید. تنها کافی است که تگِ بستهبندی (Packing Tag) را مشخص کنید تا یونیتی همه مشکلات را حل کند و به راحتی حجم بازیتان را به شکل قابل توجهی کاهش دهد.
نکته شماره ۳: کتابخانه و dllهای بدون استفاده را حذف کنید
شما باید امکان حذفِ (Stripping) کتابخانهها و dllهایِ بدونِ استفاده را فعال کنید، البته به شرط اینکه مشکلی برای پروژهتان و پلاگینهای مورد استفاده آن پیش نیاید. بازی دو بعدی شما احتمالا از بسیاری از امکانات یونیتی از جمله فیزیک استفاده نمیکند که فضای زیادی از بازیتان را اشغال کرده، بنابراین میتوانید با این تکنیک از شر این فضای اضافی خلاص شوید.
نکته شماره ۴: مطمئن شوید که Assetهای غیرضروری را در پروژه اضافه نکردهاید
اگر یک گیم آبجکت غیرفعال در بازی دارید، یونیتی در هنگام خروجی گرفتن از بازی تمام رفرنسهایی که آن گیم آبجکت مورد استفاده قرار داده را درون فایل خروجی قرار میدهد، حتی اگر این گیم آبجکت هیچگاه درون بازی فعال نشود و مورد استفاده قرار نگیرد. پنجره Hierarchy را از گیم آبجکتهای غیر ضروری و رفرنسهای آن به Assetهایِ بدون کاربرد پاکسازی کنید. همچنین هر فایلی که درون پوشه Resources باشد نیز در خروجی بازیتان اضافه میشود، پس مراقب این موضوع باشید!
بهینهسازی در استفاده از حافظه
سیستم عامل گوشیهای هوشمند با آپگرید شدن، نیازمند RAM بیشتری شدهاند که در نتیجه این امر، فضایِ حافظهی کمی برای بازی شما خالی خواهد ماند. با این شرایط بسیار منطقی است که تا حد ممکن تعداد Assetهای بازیتان که در حافظه لود میشود را کاهش دهید. اولین دلیلی که باعث میشود اپلیکیشنی توسط سیستمعامل بسته شود، فشار حافظه (Memory Pressure) است. درست متوجه شدید، کِرَشهای تصادفی بازیتان، اکثرا کِرَشهایی به دلیل مشکلات حافظه هستند.
نکته شماره ۵: اسپرایتها را …هوشمندانه….بستهبندی کنید (Pack)
وقتی بازی را اجرا میکنید، فایلها از حالت فشرده خارج میشوند و دوباره فضای زیادی را اشغال خواهند کرد. اما اگر کارتان را درست انجام داده باشید و اسپرایتها را به شکل مناسب بستهبندی کرده باشید، فضای زیادی از RAM را حفظ خواهید کرد. قاعده کلی این است که تصاویری که قرار است باهم نشان داده شوند را با هم بسته بندی کنید و سعی کنید فضای خالی زیادی در Atlas نداشته باشید. برای مثال باید تصاویر مرتبط با رابط کاربری بازیتان را درون یک اتلس مجزا قرار دهید. اما در صورتی که بخش بزرگی از آیتمهای رابط کاربریتان خیلی مورد استفاده قرار نمیگیرد، باید اسپرایتهای آن را درون یک اتلس دیگر ذخیره کنید. نکتهی مهم دیگر اینکه از اسپرایتهای ۹ قسمتی در رابط کاربری استفاده کنید و به هیچ وجه از تصاویر پسزمینه خیلی بزرگ استفاده نکنید، به هیچ وجه. (در عوض سعی کنید تصویر پسزمینه را از تایلها یا اسپرایتهای سادهتر بسازید). و نکتهی پایانی اینکه Mipmaps را غیرفعال کنید.
نکته شماره ۶: کیفیت تصاویر و فایلهای صوتی را کم کنید
آیا مطمئنید که در بازیتان به کاراکتری با طول یا عرض ۱۰۲۴ پیکسل نیاز دارید؟ به نظرم نیازی به این کیفیت نداشته باشید چون رزولوشن رایج در بین گوشیها ۱۰۲۴x۷۶۸ است. (مگر اینکه میخواهید کاراکترتان، هماندازه نمایشگر گوشی باشد). آیا موزیک بازیتان یک لوپ ۳ دقیقهای است؟ شاید بتوانید آن را به ۳۰ ثانیه کاهش دهید. این موضوع خیلی رایج است که بازی ساخته شده از حداکثر کیفیت گوشیها بیشتر باشد. اگر کیفیت تصاویر و فایلهای صوتی را کاهش دهید اکثر افراد متوجه این تفاوت در گوشیهایشان نخواهند شد.
نکته شماره ۷: مراقب رفرنسها در صحنه بازی (scene) باشید
بگذارید اتفاقی که برای خودم رخ داد را برایتان تعریف کنم. در گذشته در حال توسعه و ساخت یک بازی بودم که در آن تصاویر زیادی وجود داشت. بر اساس انتخاب بازیباز، پریفبی از لیست انتخاب و تثبیت و تصویری ظاهر میشد. این لیست بسیار طولانی بود. در کمال تعجبم این لیستِ رفرنس باعث این میشد تا تمام پریفبها و تصاویر، درون حافظه RAM بارگذاری شوند که اصلا خوب نبود. نکتهی اخلاقی این داستان؟ پروفایلر هیچگاه به شما دروغ نمیگوید، پس همیشه از آن استفاده کنید.
نکته شماره ۸: اگر نیاز شد از پوشه منابع (Resource Folder) استفاده کنید
راهکار برای مشکل قبلی این است که تمام پریفبها و Assetها را درون پوشه منابع ببرید و هر زمان که نیاز داشتید، همان آیتم را به شکل پویا بارگذاری کنید. این روش مثل استفاده از رفرنس خیلی راحت و بیدردسر نیست زیرا شما باید همواره آدرس Assetها را معین کنید و اگر ناخواسته فایلی را به آدرس دیگر منتقل کردید، با خطا روبرو خواهید شد. اما میتوانید به کمک اسکریپتنویسی، ادیتوری برای این منظور بسازید تا این مشکل را برطرف کند.
بهینهسازی در استفاده از CPU
یک بازی کند، یک بازی مرده به حساب میآید. بازیبازها در خیلی از موارد با شما کنار میآیند و سطح معمولی بازیتان را نادیده میگیرند. اگر بازیتان رابط کاربری بد، داستان معمولی، گرافیک ضعیف و… داشته باشد احتمالا پلیرها شما را خواهند بخشید، اما در مورد پرفورمنس ضعیف، جای هیچگونه بخششی نخواهد بود. باید محدودیتهایتان را بشناسید و در مواقعی که مجبور هستید خیلی از چیزها را به بهای نرخ فریم قابل قبول قربانی کنید. بله، این یک واقعیت تلخ است!
نکته شماره ۹: مراقب ساختارهای آبنباتی (Candy Syntax ) باشید، مانند حلقه Foreach
این نکته یکی از تکراریترین راهکارها برای بهینهسازی CPU در یونیتی است. زیرا یونیتی از ورژنهای قدیمی دات نت فریمورک استفاده میکند که به اندازه کافی بهینه نیستند. پس بهتر است به جای Foreach از حلقهی for استفاده کنید و به جای لیست از آرایه کمک بگیرید.
(البته این موضوع مربوط به ورژنهای قدیمی یونیتی است که در زمان نوشتن مقاله مورد استفاده قرار میگرفته و در نسخههای جدید یونیتی این مشکل برطرف شده. اکنون یونیتی از NET 4.x. پشتیبانی میکند.)
نکته شماره ۱۰: لاگهای کنسول را حذف کنید
در یونیتی متغیرهایی از نوع رشته (Strings) باعث کند شدن بازیتان میشوند، بنابراین تا میتوانید از تغییر دادن رشتهها بپرهیزید. به همین دلیل حتما در انتهای توسعه بازی، تمامی Debug.Logهایی که برای لاگ گرفتن و دیباگ کردن بازی نوشتهاید را حذف کنید، مخصوصا آنهایی که در Update یا حلقهها هستند. از اینکه تغییری به این کوچکی چقدر میتواند روی کارایی بازی تاثیر بگذارد شگفتزده خواهید شد.
نکته شماره ۱۱: بهینه سازی را از پایین به بالا شروع کنید
وقتی در حال بهینهسازی بازیتان هستید و پروفایلر را چک می کنید، کلاسهایی که باعث کند شدن بازی شدهاند را شناسایی کنید و سپس شروع به بهینهسازی کلاسی کنید که در انتها و پایینترین نقطه قرار دارد. اینها کلا هایی هستند که کلاسهای دیگر هم از آنها استفاده میکنند، برای مثال کلاسهای هوش مصنوعی (AI) یا مسیریابی (Pathfinding). اگر شانس بیاورید احتمالا با بهینهسازی کلاسهای پایین، نیازی به تغییر در کلاسهای بالا نداشته باشید.
نکته شماره ۱۲: ابتدا به دنبال گلوگاههای (bottlenecks) کلاسیک بگردید، مثل حلقههای تودرتو یا اسکریپتهایی که آیتمهای زیادی تولید (instantiate) میکنند
تصور کنید که اسکریپتی به اسم PlayerController و اسکریپت دیگری به نام EnemyController دارید. اگر قرار است در بازیتان 100 دشمن به صورت همزمان وجود داشته باشند و کنترل شوند، پس این احتمال وجود دارد که اسکریپت EnemyController صد مرتبه بیشتر باعث کند شدن بازی تان شود. درنتیجه شروع به بهینه سازی این اسکریپت کنید و ابتدا سراغ راهکارهای کلاسیکی مانند این موارد بروید: فراخوانیهای کندی مانند تابع FindObjectOfType را حذف کنید. استفاده از این توابع در Update ممنوع است. از کَش کردن به کمک متغیرها استفاده کنید. همچنین کلاسهای Singleton نیز میتواند در زمینه بهینهسازی به شما کمک کند.
بهینهسازی خوبی را برایتان آرزومندم! و به خاطر داشته باشید که بازیای بسازید که بازیبازها مستحق آن باشند، پس بازیِ عالی بسازید.
نویسنده: محمد علیزاده