چند بار شده که سیستمهای یکسانی را در پروژههای مختلف یونیتی بازنویسی کردهاید؟ یا این که پوشههای کامل را از یک پروژه قدیمی کپی کردهاید و سپس ساعات زیادی را صرف رفع ارجاعات، تغییر نامفضاها و تطبیق کد با یک معماری کمی متفاوت کردهاید؟ این تکرارها نه تنها وقتگیر هستند، بلکه سرعت توسعه را کند کرده و چالشهای نگهداری را در بین پروژهها ایجاد میکنند. این راهنما شما را با مجموعهای از پکیجهای ماژولار و قابل استفاده مجدد در یونیتی آشنا میکند که میتوانید آنها را در هر پروژهای قرار داده و سرعت توسعه خود را افزایش دهید. شما این پکیجها را یک بار به صورت بستههای نرمافزاری ساخته و از طریق Git در هر کجا که نیاز داشتید نصب میکنید، به جای این که هر بار همان سیستمها را دوباره پیادهسازی کنید.
یکی از بزرگترین موانع در مسیر توسعه سریع و کارآمد بازی با موتور یونیتی، بازنویسی مداوم سیستمهای پایهای است. توسعهدهندگان اغلب مجبور میشوند کدهایی مانند مدیریت اولیهسازی کنترلرها، سیستم ذخیرهسازی داده، مدیریت رابط کاربری و انیمیشنها را برای هر پروژه جدید از نو بنویسند یا با کپیگیری از پروژههای قبلی، با مشکلات فنی مانند شکستن ارجاعات، تداخل نامفضاها و عدم سازگاری معماری دست و پنجه نرم کنند. این رویکرد نه تنها منابع ارزشمند زمانی را هدر میدهد، بلکه باعث ناسازگاری و افزایش خطا در طول چرخه حیات پروژه میشود. راه حل اصولی برای غلبه بر این چالش، حرکت به سمت معماری ماژولار با استفاده از قابلیت Package Manager در یونیتی است.
هدف این مقاله، ارائه راهحلی عملی برای خروج از چرخه توسعه تکراری است. ما چهار پکیج اصلی و مستقل را طراحی کردهایم که هر یک مسئولیت یک بخش حیاتی از بازی را به عهده میگیرند. این پکیجها به گونهای طراحی شدهاند که به راحتی قابل حمل و استفاده مجدد در پروژههای مختلف باشند.
استفاده از این مجموعه پکیج مزایای متعددی را برای تیمهای توسعه بازی به ارمغان میآورد. اولین و مهمترین مزیت، صرفهجویی چشمگیر در زمان توسعه است. شما سیستمهای پایه را تنها یک بار به دقت طراحی و پیادهسازی میکنید و در پروژههای بعدی تنها با یک خط در فایل manifest.json آن را اضافه میکنید. دومین مزیت، کاهش خطاها و افزایش پایداری است. از آنجایی که هر پکیج به صورت مستقل تست و نگهداری میشود، باگها سریعتر شناسایی و رفع شده و اصلاحات به تمام پروژههایی که از آن پکیج استفاده میکنند منتقل میشود. در نهایت، این رویکرد انسجام معماری را در بین پروژههای یک تیم یا حتی یک استودیو تضمین میکند و onboarding توسعهدهندگان جدید را بسیار سادهتر میسازد.
در طول این آموزش جامع، شما به صورت گام به گام با فرآیند ساخت هر یک از این پکیجها آشنا خواهید شد. مباحثی که پوشش داده میشوند شامل نحوه ایجاد یک پکیج یونیتی با استفاده از Package Manager، ساختار استاندارد یک پکیج، پیادهسازی یک جریان متمرکز برای مقداردهی اولیه با استفاده از UniTask برای عملیات غیرهمزمان در thread اصلی یونیتی، نحوه استفاده پکیج داده از IDataProvider و MemoryPack برای ذخیره و بارگذاری محلی، و finally مدیریت پاپآپها و صفحهها با انیمیشنهای DoTween در پکیج UI خواهد بود. تمامی این پکیجها به صورت کامل مستند شده و لینک مخزن GitHub آنها در انتهای مقاله ارائه شده است تا بتوانید به کد منبع کامل دسترسی داشته باشید.
با به پایان رساندن این راهنما، شما نه تنها بر ساخت چهار پکیج قدرتمند مسلط خواهید شد، بلکه درکی عمیق از فلسفه توسعه ماژولار در یونیتی به دست خواهید آورد. این دانش به شما امکان میدهد تا ماژولهای اختصاصی خود را طراحی کرده و یک کتابخانه شخصی از کامپوننتهای قابل استفاده مجدد برای تسریع تمام پروژههای آینده خود ایجاد کنید.
اولین و پایهایترین پکیج در این مجموعه، `com.core.initializer` است. هدف اصلی این پکیج، حل یکی از چالشبرانگیزترین مسائل در توسعه پروژههای Unity است: مدیریت مقداردهی اولیه مؤلفههای مختلف بازی بهصورت متمرکز و غیروابسته. بدون چنین سیستمی، شما مجبورید در هر پروژهای مکانیزمی برای راهاندازی کنترلرها، سرویسها و مدیرهای مختلف پیادهسازی کنید که اغلب منجر به کدهای تکراری، وابستگیهای پیچیده و مشکلات زمان اجرا میشود. این پکیج با ارائه یک جریان استاندارد و قابل اعتماد برای مقداردهی اولیه، پایه محکمی برای ماژولهای دیگر و کل پروژه شما فراهم میکند.
این پکیج سه وظیفه اصلی را بر عهده دارد:
برای شروع، از طریق پنجره Package Manager در Unity (Window → Package Manager) یک پکیج جدید با نام `com.core.initializer` ایجاد میکنید. Unity به طور خودکار فایلهای ضروری مانند `package.json` و فایل تعریف اسمبلی (asmdef) را برای شما میسازد. یکی از مهمترین تصمیمات در این پکیج، انتخاب کتابخانه برای عملیات ناهمزمان (Async) است. از آنجایی که Unity روی thread اصلی اجرا میشود و C# Tasks استاندارد ممکن است با وضعیت ویرایشگر (مثل خروج از حالت Play) به خوبی تعامل نداشته باشند، این پکیج از `UniTask` استفاده میکند. UniTask به طور خاص برای استفاده در Unity طراحی شده و این مشکلات را برطرف میکند. شما باید UniTask را از طریق OpenUPM به پروژه توسعه خود اضافه کرده و سپس آن را به عنوان یک وابستگی در فایل `package.json` پکیج Initializer معرفی کنید.
معماری این پکیج از الگوی MVC الهام گرفته شده است. در اینجا، کنترلرها هم مسئولیت منطق بازی و هم فرآیند مقداردهی اولیه خود را بر عهده دارند. ساختار پیشنهادی پکیج به این صورت است:
استفاده از این پکیج بسیار ساده است. برای افزودن یک کنترلر جدید به سیستم، مراحل زیر را دنبال کنید:
اگرچه این پکیج پایه بسیار قدرتمندی فراهم میکند، اما در نسخه فعلی دو محدودیت اصلی دارد. اول، مدیریت وابستگیهای بین کنترلرهای مختلف میتواند Challenging باشد. دوم، احتمال به وجود آمدن وابستگیهای حلقوی (Circular Dependencies) وجود دارد. برای پروژههای بزرگتر، ادغام این پکیج با یک چارچوب تزریق وابستگی (Dependency Injection) مانند VContainer یا Reflex میتواند این محدودیتها را به طور کامل برطرف کند. مخزن GitHub این پکیج ممکن است در آینده با چنین قابلیتی به روز شود.
در نهایت، پکیج `com.core.initializer` با سادهسازی فرآیند راهاندازی، یکنواختی را در بین پروژههای شما به ارمغان میآورد و باعث صرفهجویی قابل توجهی در زمان توسعه میشود. این پکیج به عنوان شالوده اصلی مجموعه عمل میکند و امکان کارکرد صحیح و یکپارچه پکیجهای بعدی مانند مدیریت داده و UI را فراهم میسازد.
یکی از چالشهای بزرگ در توسعه پروژههای مختلف یونیتی، پیادهسازی مکرر سیستم مدیریت ذخیره و بازیابی دادهها است. پکیج com.core.data برای حل این مشکل طراحی شده است. این پکیج یک سیستم ماژولار و قدرتمند برای مدیریت دادههای محلی (و در آینده ابری) ارائه میدهد که با استفاده از سریالایز باینری MemoryPack، سرعت و کارایی بالایی دارد. هدف اصلی این پکیج، ایجاد یک لایه انتزاعی (Abstraction Layer) است که به شما امکان میدهد بدون تغییر در منطق اصلی برنامه، backend ذخیرهسازی را عوض کنید یا گسترش دهید.
قلب این پکیج، اینترفیس IDataProvider است. این اینترفیس عملیات اصلی مانند Save، Load و Delete را به صورت متدهای async (با استفاده از UniTask) تعریف میکند. این طراحی به معنای آن است که شما میتوانید ارائهدهندههای مختلفی برای پلتفرمها یا نیازمندیهای متفاوت پیادهسازی کنید. به عنوان مثال، یک ارائهدهنده محلی (LocalDataProvider) که دادهها را روی دستگاه ذخیره میکند، یک ارائهدهنده JSON مبتنی بر Newtonsoft، یا حتی یک ارائهدهنده ابری برای همگامسازی دادهها در دستگاههای مختلف. در این معماری، DataController به عنوان یک کنترلر مرکزی، مسئولیت استفاده از ارائهدهنده انتخاب شده و مدیریت چرخه عمر دادهها را بر عهده دارد.
پکیج com.core.data به طور پیشفرض از MemoryPack برای سریالایز کردن دادهها استفاده میکند. MemoryPack یک کتابخانه سریالایز باینری فوقالعاده سریع است که عملکرد به مراتب بهتری نسبت به فرمتهای متنی مانند JSON دارد. ارائهدهنده محلی (LocalDataProvider) دادههای سریالشده را در قالب فایلهای باینری در مسیر Application.persistentDataPath/Data ذخیره میکند. این مسیر بر اساس پلتفرم به طور خودکار توسط یونیتی مدیریت میشود و مکان استانداردی برای ذخیره دادههای پایدار برنامه است. برای اضافه کردن MemoryPack به پروژه، باید آن را از OpenUPM (با نسخه ۱.۱۰.۰ یا بالاتر) نصب کرده و آن را به عنوان وابستگی در فایل package.json پکیج داده اضافه کنید.
DataController خود یک IController است که توسط پکیج com.core.initializer مدیریت میشود. این به این معنی است که سیستم ذخیرهسازی دادهها به طور خودکار و قبل از بارگذاری اولین صحنه مقداردهی اولیه میشود. یکی از ویژگیهای مفید DataController، ردیابی تاریخچه نسخه برنامه کاربر است. این قابلیت برای سناریوهایی مانند نمایش پیام بروزرسانی، جلوگیری از اجرای نسخههای قدیمی یا مهاجرت دادهها هنگام تغییر ساختار در نسخههای جدید برنامه بسیار حیاتی است. کنترلر به طور خودکار نسخه فعلی برنامه را ذخیره کرده و با نسخه قبلی مقایسه میکند.
برای استفاده از این پکیج در یک پروژه، پس از نصب آن از طریق Git، کافیست یک کلاس DataController ایجاد کنید که از IController ارثبری کند. این کنترلر منطق ذخیره و بازیابی مدلهای داده خاص برنامه شما را با استفاده از IDataProvider پیادهسازی میکند. سپس میتوانید پس از اتمام مقداردهی اولیه همه کنترلرها، از طریق ControllerHandler.GetController<DataController>() به آن دسترسی پیدا کرده و عملیات مورد نظر را انجام دهید.
استفاده از این پکیج بسیار ساده است. پس از راهاندازی، شما یک سیستم ذخیرهسازی قابل اعتماد و سریع در اختیار دارید که به راحتی قابل توسعه است. اگر نیاز به تغییر backend ذخیرهسازی داشته باشید (مثلاً اضافه کردن قابلیت ذخیره ابری)، کافیست یک کلاس جدید ایجاد کنید که IDataProvider را پیادهسازی کند و سپس آن را در DataController خود ثبت کنید. این ماژولاریتی باعث صرفهجویی قابل توجهی در زمان توسعه پروژههای آینده میشود. همانند دیگر پکیجها، این پکیج نیز روی GitHub در دسترس است و میتوانید ایدههای خود مانند پیادهسازی ذخیرهسازی ابری را از طریق Issue مطرح کنید.
در نهایت، پکیج com.core.data با تمرکز بر ماژولاریتی، کارایی و یکپارچهسازی روان، یک پایه مستحکم برای مدیریت دادهها در هر پروژه یونیتی فراهم میکند و شما را از نوشتن کدهای تکراری برای سیستم ذخیرهسازی در هر پروژه جدید بینیاز میسازد.
پکیج com.core.dotween در واقع یک پوشش (Wrapper) برای موتور انیمیشنسازی قدرتمند و پرکاربرد DOTween است. هدف اصلی از ایجاد این پکیج، فراهم کردن یک وابستگی استاندارد و قابل مدیریت برای سایر پکیجهای هسته، به ویژه پکیج رابط کاربری (com.core.ui) است. با بستهبندی DOTween به عنوان یک پکیج یونیتی، میتوانید به راحتی آن را از طریق مدیریت بسته (Package Manager) و با استفاده از آدرس Git در هر پروژهای نصب کنید. این کار نیاز به کپیکردن دستی فایلهای دارایی (Asset) در هر پروژه جدید را از بین میبرد و وابستگیهای پروژه شما را تمیز و حرفهای نگه میدارد. این پکیج به خودی خود منطق پیچیدهای ندارد و تنها نقش یک پل ارتباطی را ایفا میکند.
برای شروع، باید خود DOTween را از فروشگاه داراییهای یونیتی (Unity Asset Store) دانلود و به پروژه توسعهای خود اضافه کنید. معمولاً پس از ایمپورت این دارایی، پنجره تنظیمات DOTween به طور خودکار باز میشود. در این پنجره، حتماً روی گزینه "Create ASMDEF" کلیک کنید. این عمل فایلهای تعریف اسمبلی (Assembly Definition Files) لازم برای DOTween را ایجاد میکند تا سایر پکیجها بتوانند به آن ارجاع دهند. در مرحله بعد، یک پکیج جدید در مدیریت بسته یونیتی با نامی مانند com.core.dotween ایجاد کنید. سپس پوشه Demigiant (که حاوی فایلهای اصلی DOTween است) را از مسیر Assets/Plugins به پوشه ریشه پکیج جدید خود منتقل کنید. با این کار، محتوای DOTween به بخشی از پکیج مستقل شما تبدیل میشود.
پس از ایجاد پکیج com.core.dotween، نوبت به استفاده از آن در پکیج رابط کاربری (com.core.ui) میرسد. در فایل package.json پکیج UI، باید com.core.dotween را به عنوان یک وابستگی (Dependency) اضافه کنید. به همین ترتیب، در فایل تعریف اسمبلی (asmdef) پکیج UI نیز باید یک Reference به پکیج dotween اضافه شود. این تنظیمات به کامپایلر یونیتی اجازه میدهد که فضای نام DG.Tweening را تشخیص دهد و اسکریپتهای پکیج UI بتوانند از کلاسها و متدهای DOTween برای ایجاد انیمیشنهای روان استفاده کنند. این وابستگی صحیح، پایه و اساس انیمیشنهای نمایش و مخفیسازی پاپآپها را فراهم میآورد.
کاربرد اصلی پکیج dotween در کلاس BasePopup از پکیج UI نمایان میشود. این کلاس از DOTween برای ایجاد انیمیشنهای مقیاس (Scale) در هنگام نمایش و مخفیسازی پاپآپ استفاده میکند. به عنوان مثال، متد ShowAnimation میتواند از یک انیمیشن ساده از مقیاس صفر به مقیاس کامل با استفاده از تابع Elastic.InOut بهره ببرد. یکی از نکات مهم در پیادهسازی، استفاده از متد SetUpdate(true) است. این کار باعث میشود انیمیشنها نسبت به مقیاس زمانی بازی (Time Scale) بیتفاوت باشند و حتی زمانی که بازی در حالت وقفه (Pause) قرار دارد، به نرمی اجرا شوند. DOTween همچنین با فراهم کردن Callbackهای قابل اطمینان، امکان فراخوانی رویدادهای Showed و Hidden را پس از اتمام انیمیشنها میسر میسازد.
با بستهبندی DOTween به عنوان یک پکیج، چندین مزیت مهم به دست میآید. اولاً، مدیریت نسخهها سادهتر میشود و میتوانید مطمئن باشید که تمام پروژهها از نسخه یکسان و سازگاری از موتور انیمیشنسازی استفاده میکنند. ثانیاً، فرآیند افزودن قابلیت انیمیشن به پروژههای جدید به سرعت و با افزودن یک خط در فایل manifest.json انجام میپذیرد. ثالثاً، این ماژولاریته به شما اجازه میدهد که در آینده، در صورت لزوم، موتور انیمیشنسازی دیگری را جایگزین کنید بدون اینکه نیاز به تغییرات گسترده در پکیج اصلی UI داشته باشید. در نهایت، پکیج com.core.dotween نمونهای عالی از چگونگی استانداردسازی وابستگیهای خارجی در یک چارچوب ماژولار و قابل استفاده مجدد است.
پکیج com.core.ui یک سیستم مرکزی برای مدیریت رابط کاربری در بازیهای یونیتی ارائه میدهد. این پکیج با تفکیک پاپآپها و اسکرینها، امکان مدیریت یکپارچهای را فراهم میکند. پاپآپها به صورت پشتهای مدیریت میشوند که آخرین پاپآپ نمایش داده شده، اولین موردی است که بسته میشود. اسکرینها به صورت تکفعال عمل میکنند، به این معنی که تنها یک اسکرین در هر زمان میتواند فعال باشد. این معماری از تکرار منطق نمایش در بخشهای مختلف بازی جلوگیری کرده و توسعه را تسریع میکند.
کلاس BasePopup از موتور انیمیشن DoTween برای ایجاد انیمیشنهای نمایش و مخفیسازی استفاده میکند. این کلاس دارای متدهای Show و Hide است که به صورت ناهمگام عمل کرده و امکان پیکربندی مدت زمان و نوع انیمیشنها را فراهم میکنند. از ویژگیهای مهم این پیادهسازی میتوان به استفاده از SetUpdate(true) اشاره کرد که انیمیشنها را مستقل از timescale میکند. کلاس BaseScreen سادهتر عمل کرده و تنها با فعال و غیرفعال کردن GameObject کار میکند، اما ساختار ناهمگام آن امکان توسعه آینده را فراهم میسازد.
UIController قلب سیستم رابط کاربری است که اینترفیس IController را پیادهسازی میکند. این کنترلر مسئولیت نمونهسازی UIParent از Resources، مدیریت پشته پاپآپها و نمایش اسکرینها را بر عهده دارد. این کلاس با کش کردن نمونهها بر اساس نوع، کارایی سیستم را افزایش میدهد. مسیر پیشفرض برای بارگذاری پرتابها به صورت Resources/UIPrefabs/Popups/ و Resources/UIPrefabs/Screens/ تعیین شده و نام پرتاب باید با نام نوع کلاس مطابقت داشته باشد.
برای استفاده از پکیج UI در پروژه یونیتی، کافی است آن را از طریق Git به manifest.json اضافه کنید. سپس میتوانید با استفاده از متدهای PushPopup و PopPopup برای مدیریت پاپآپها و متد ShowScreen برای نمایش اسکرینها اقدام کنید. این سیستم به گونهای طراحی شده که به راحتی با پکیج Initializer و Data یکپارچه شده و امکان مدیریت کامل چرخه عمر رابط کاربری را فراهم میکند.
پکیج سیستم رابط کاربری یونیتی نمونهای عالی از ماژولاریتی و قابلیت استفاده مجدد است. با پیادهسازی این پکیج، نه تنها زمان توسعه رابط کاربری در پروژههای مختلف کاهش مییابد، بلکه قابلیت نگهداری و یکپارچگی کد نیز بهبود قابل توجهی پیدا میکند. توصیه میشود برای پروژههای بزرگتر، از سیستمهای تزریق وابستگی مانند VContainer برای مدیریت بهتر وابستگیها استفاده کنید. همچنین میتوانید این پکیج را با افزودن انواع جدید المانهای UI مانند toast message یا tooltip گسترش دهید.