۶۷ ترفند و بهترین تمرین ها برای Unity (همه نسخه ها)

رتبه: 5 ار 1 رای SSSSS
آرم یونیتی
نویسنده: میلاد حیدری زمان مطالعه 42 دقیقه
Banner Image

در این مطلب بیش از ۶۷ نکته و تمرین برای یادگیری بهتر نرم افزار یونیتی را به شما آموزش می دهیم. نکاتی که بسیار کاربردی هستند و سرعت کار و تمیزی کار را افزایش می دهند.

Workflow

۱- از ابتدا در مورد مقیاس تصمیم بگیرید و همه چیز را در همان مقیاس بسازید. اگر این کار را نکنید، ممکن است بعداً نیاز به دوباره کاری کردن موجودیت ها داشته باشید (به عنوان مثال، انیمیشن همیشه مقیاس درستی ندارد). برای بازی های سه بعدی، معمولاً استفاده از ۱ Unity unit = 1m بهترین است. برای بازی های دو بعدی که از نور و فیزیک استفاده نمی کنند، ۱ Unity unit = 1 pixel (با وضوح “طراحی”) معمولاً خوب است. برای UI (و بازی های دو بعدی)، وضوح طراحی را انتخاب کنید (ما از HD یا ۲xHD2 استفاده می کنیم) و تمام موجودیت ها را برای مقیاس گذاری در آن وضوح طراحی کنید.

۲- هر صحنه را قابل اجرا کنید. این کار را برای جلوگیری از تغییر صحنه برای اجرای بازی انجام دهید تا بتوانید سریعتر تست کنید. اگر اشیایی داشته باشید که در بین هر بار لود صحنه باقی بمانند و در همه صحنه های شما مورد نیاز باشد، ممکن است مشکل باشد. یکی از روش های انجام این کار استفاده از اشیا ثابت است که به صورت مجزا ساخته می شوند که در صورت عدم حضور در صحنه، خودشان لود می شوند.

۳- از کنترل منبع استفاده کنید و بیاموزید که چگونه از آن به طور موثر استفاده کنید.

  • موجودیت های خود را به صورت متن مرتب کنید. در عمل صحنه ها و Prefab ها را قابل ادغام تر نمی کند، اما درک اینکه چه عواملی تغییر کرده آسان تر است.
  • استراتژی اشتراک صحنه و Prefab را اتخاذ کنید. به طور کلی، بیش از یک نفر نباید در یک صحنه یا Prefab کار کند. برای یک تیم کوچک هیچ کس قبل از شروع کار بر روی صحنه یا Prefab کار نمی کند. تعویض نشانه های فیزیکی که نشان دهنده مالکیت صحنه در اطراف است، ممکن است مفید باشد (فقط در صورت داشتن نماد صحنه روی میز خود مجاز به کار در صحنه هستید).
  • از برچسب ها به عنوان نشانک استفاده کنید.
  • تصمیم بگیرید و به یک استراتژی شاخه ای پایبند باشید. از آنجا که نمی توان صحنه ها و Prefab ها را به آرامی ادغام کرد، شاخه بندی کمی پیچیده تر است. با این وجود اگر تصمیم دارید از شاخه ها استفاده کنید، باید با صحنه و استراتژی اشتراک Prefab شما کار کند.
  • با دقت از زیر مدول ها استفاده کنید. زیر مدول ها می توانند راهی عالی برای حفظ کد قابل استفاده مجدد باشند. اما چند نکته مهم وجود دارد:
  • متافایل ها به طور کلی در چندین پروژه سازگار نیستند. به طور کلی مشکلی برای کد اشیا غیر Monobehaviour یا غیر اسکریپت نویسی نیست، اما برای اشیا MonoBehaviours و اسکریپتی با استفاده از زیر مدول ها می توانند باعث شوند کد از دست برود.
  • اگر روی پروژه های زیادی کار می کنید (از جمله یک یا چند مورد در زیر مدول ها)، گاهی اوقات می توانید به روزرسانی ناگهانی را بدست آورید که برای ایجاد ثبات کد در همه پروژه ها مجبورید پروژه های مختلف را چندین بار ادغام کنید. (و اگر شخص دیگری در حال انجام تغییراتی باشد، می تواند به تغییرات پایدار تبدیل شود). یکی از راه های به حداقل رساندن این اثر این است که همیشه از پروژه هایی که به آن ها اختصاص داده شده تغییراتی در زیر مدول ها ایجاد شود. به این ترتیب، پروژه هایی که از زیر مدول استفاده می کنند فقط باید مجبور به بازیابی شوند. آن ها هرگز نیازی به عقب بردن ندارند.

۴- صحنه های تست و کد را جداگانه نگه دارید. موجودیت ها و اسکریپت های موقت را به مخزن اختصاص دهید و پس از پایان کار آن ها را از پروژه حذف کنید.

۵- اگر ابزارها (به ویژه Unity) را ارتقا دادید ، این کار را به طور همزمان انجام دهید. وقتی پروژه ای را با نسخه ای متفاوت از گذشته باز می کنید، Unity در حفظ پیوندها بسیار خوب است، اما گاهی اوقات وقتی افراد با نسخه های مختلف کار می کنند، پیوندها از بین می روند.

۶- موجودیت های شخص ثالث را در یک پروژه تمیز وارد کنید و پکیج جدیدی را برای استفاده شخصی خود از آنجا صادر کنید. موجودیت ها ممکن است گاهی اوقات هنگام وارد کردن مستقیم آن ها در پروژه، با مشکل روبرو شوند:

  • ممکن است برخوردهایی (فایل یا نام) وجود داشته باشد، خصوصاً موجودیت هایی که در ریشه پوشه Plugins فایل دارند یا از موجودیت های Standard Assets در مثال های خود استفاده می کنند.
  • آن ها ممکن است سازمان یافته نباشند و فایل های خود را در سرتاسر پروژه خود قرار دهند. این مسئله به ویژه اگر تصمیم دارید از آن استفاده نکنید و می خواهید آن را حذف کنید، مشکل است.

برای وارد کردن ایمن تر موجودیت این مراحل را دنبال کنید:

۱- یک پروژه جدید بسازید و موجودیت را وارد کنید.

۲- مثال ها را اجرا کنید و مطمئن شوید که آن ها کار می کنند.

۳- موجودیت را در ساختار پوشه ای مناسب تری سازماندهی کنید. (ما معمولاً ساختار پوشه خود را روی موجودیت اجرا نمی کنیم. اما مطمئن می شویم که همه فایل ها در یک پوشه قرار دارند و هیچ فایلی در مکان های مهم وجود ندارد که بتواند فایل های موجود را در پروژه ما بازنویسی کند.)

۴- مثال ها را اجرا کنید و مطمئن شوید که آن ها هنوز کار می کنند. (بعضی اوقات، هنگام جابجایی موجودیت ها دچار ایراد می شدند، اما به طور کلی این مسئله مشکلی ایجاد نمی کند).

۵- اکنون تمام مواردی را که به آن ها نیازی نخواهید داشت (مانند مثال ها) حذف کنید.

۶- اطمینان حاصل کنید که موجودیت هنوز کامپایل می شود و Prefab ها هنوز تمام پیوندهای خود را دارند. اگر چیزی برای اجرا باقی مانده است، آن را تست کنید.

۷- اکنون همه موجودیت ها را انتخاب کرده و پکیج را صادر کنید.

۸- به پروژه خود import کنید.

دانلود رایگان آموزش صفر تا صد نرم افزار یونیتی (فیلم فارسی+ جزوه pdf)

۷- روند ساخت خود را خودکار کنید. این حتی برای پروژه های کوچک نیز مفید است، اما به ویژه هنگامی مفید است که:

  • شما نیاز به ساخت نسخه های مختلف زیادی از بازی دارید،
  • سایر اعضای تیم با درجات مختلف دانش فنی نیاز به ساخت دارند، یا
  • قبل از ساخت، باید اصلاحات کوچکی در پروژه ایجاد کنید.

۸- راه اندازی خود را مستند کنید. بیشتر اسناد باید در کد باشد، اما موارد خاص باید خارج از کد مستند شوند. بررسی طراحان از طریق کد برای راه اندازی، هدر دادن وقت است. راه اندازی های مستند باعث بهبود کارایی (در صورت موجود بودن اسناد) می شوند.

موارد زیر را مستند کنید:

  • موارد استفاده از برچسب
  • موارد استفاده از لایه (برای برخورد، از بین بردن، و چه چیزی باید در چه لایه ای باشد).
  • عمق GUI برای لایه ها (چه چیزی باید روی دیگری نمایش داده شود).
  • راه اندازی صحنه
  • ساختار پیش ساخته Prefab های پیچیده.
  • اولویت اصطلاحات
  • ایجاد راه اندازی

برنامه نویسی عمومی

۹- تمام کدهای خود را در یک namespace قرار دهید. با این کار از برخورد کد بین کتابخانه های شخصی و کد شخص ثالث جلوگیری می شود. اما برای جلوگیری از برخورد با طبقات مهم به namespace اعتماد نکنید. حتی اگر از namespace های مختلف استفاده می کنید ، از “Object” یا “Action” یا “Event” به عنوان نام کلاس استفاده نکنید.

۱۰- از Assertion ها استفاده کنید. Assertion ها برای آزمایش نامتغیرها در کد مفید هستند و به رفع اشکالات منطقی کمک می کنند. Assertion ها در کلاس Unity.Assertions.Assert موجود هستند. همه آن ها شرایطی را آزمایش می کنند و در صورت عدم احراز شرایط، یک پیام خطا در کنسول می نویسند.

۱۱- از رشته ها به جز برای نمایش متن، استفاده نکنید. به طور خاص، از رشته ها برای شناسایی اشیا یا Prefab ها استفاده نکنید. موارد استثنایی وجود دارد (هنوز چند مورد وجود دارد که فقط با نام در Unity قابل دسترسی است). در چنین مواردی، آن رشته ها را به صورت ثابت در فایل هایی مانند AnimationNames یا AudioModuleNames تعریف کنید. اگر این کلاس ها غیرقابل کنترل شدند، از کلاس های تو در تو استفاده کنید تا بتوانید چیزی مانند AnimationNames.Player.Run بگویید.

۱۲- از Invoke و SendMessage استفاده نکنید. این متد های MonoBehaviour متد های دیگر را با نام صدا می زند. متد هایی که با نام فراخوانی می شوند، در کد به سختی قابل ردیابی هستند (شما نمی توانید “Usages” را پیدا کنید و SendMessage دامنه وسیعی دارد که پیگیری آن حتی دشوارتر است)

به راحتی می توانید Invoke خود را با استفاده از اقدامات Coroutines و C # اجرا کنید:

public static Coroutine Invoke(this MonoBehaviour monoBehaviour, Action action, float time)

{

return monoBehaviour.StartCoroutine(InvokeImpl(action, time));

}

private static IEnumerator InvokeImpl(Action action, float time)

{

yield return new WaitForSeconds(time);

action();

}

می توانید از این پس در monoBehaviour خود مانند این استفاده کنید:

this.Invoke(ShootEnemy); //where ShootEnemy is a parameterless void method.

اگر MonoBehaviour پایه خود را پیاده سازی کنید، می توانید Invoke خود را نیز به آن اضافه کنید.

اجرای یک گزینه ایمن تر برای SendMessage دشوارتر است. در عوض، ما معمولاً از انواع GetComponent استفاده می کنیم تا اجزای والدین، شی بازی فعلی یا فرزندان را بدست آوریم و مستقیماً فراخوانی کنیم.

۱۳- اجازه ندهید هنگام اجرای بازی اشیا تکثیری سلسله مراتب شما را بهم ریخته و شلوغ کنند. والدین آن ها را روی یک شی صحنه تنظیم کنید تا هنگام اجرای بازی در پیدا کردن چیزها راحت تر شوند. برای سهولت دسترسی به کد، می توانید از یک شی خالی بازی یا حتی یک تک بازی استفاده کنید. این شی را DynamicObjects بنامید.

۱۴- در استفاده از null به عنوان یک مقدار قانونی مراقب باشید و در جایی که می توانید از آن اجتناب کنید.

Null ها در تشخیص کد نادرست مفید هستند. با این حال، اگر عادت کنید که بی صدا از null عبور کنید، کد نادرست شما اجرا می شود و متوجه اشکال نمی شوید. بعلاوه، با عبور هر لایه از متغیرهای null، می تواند در اعماق کد ظاهر شود. ما سعی می کنیم به طور کلی از استفاده از null به عنوان یک مقدار قانونی اجتناب کنیم.

اصطلاح ترجیحی ما این است که هیچگونه بررسی null انجام ندهید و اجازه دهید کد در صورت بروز مشکل از کار بیفتد. در متد هایی که به عنوان رابط در سطح عمیق تری عمل می کنند، یک متغیر را از نظر null بررسی می کنیم و یک استثنا (exception) را به جای انتقال به متد های دیگر که ممکن است خراب باشد، throw می کنیم.

در بعضی موارد، یک مقدار می تواند به طور قانونی null باشد و باید به روشی دیگر اداره شود. در مواردی از این دست، توضیحی اضافه کنید تا توضیح دهید چه موقع و چرا چیزی می تواند null باشد.

یک سناریو معمول اغلب برای مقادیر پیکربندی شده توسط بازرس استفاده می شود. کاربر می تواند مقداری را مشخص کند، اما در صورت عدم استفاده، از مقدار پیش فرض استفاده می شود. یک روش بهتر برای انجام این کار با یک کلاس اختیاری <T> است که مقادیر T را بسته بندی می کند (کمی شبیه Nullable <T> است). برای رندر یک چک باکس می توانید از یک رندر خاص استفاده کنید و فقط در صورت تیک زدن جعبه مقدار را نشان می دهید. (متأسفانه، نمی توانید مستقیماً از کلاس عمومی استفاده کنید، باید کلاس ها را برای مقادیر خاص T گسترش دهید.)

[Serializable]

public class Optional<T>

{

public bool useCustomValue;

public T value;

}

در کد خود، می توانید از آن به صورت زیر استفاده کنید:

health = healthMax.useCustomValue ? healthMax.Value : DefaultHealthMax;

بسیاری از افراد خاطرنشان کردند که استفاده از ساختار بهتر است (مقدار گاربیج ایجاد نمی کند و پوچ نیست). با این حال، این بدان معنی است که شما نمی توانید از آن به عنوان کلاس پایه برای کلاس های non-generic استفاده کنید، بنابراین می توانید از آن برای فیلد هایی استفاده کنید که در واقع می توانند در بازرس استفاده شوند.

۱۵- اگر از Coroutines استفاده می کنید، یاد بگیرید که از آن ها به طور موثر استفاده کنید. Coroutines می تواند راهی قدرتمند برای حل بسیاری از مشکلات باشد. اما اشکال زدایی از آن ها دشوار است و شما می توانید به راحتی خود را سردرگم کنید که هیچ کس، حتی خود شما نمی تواند آن را درک کند.

باید موارد زیر را بدانید:

  • نحوه اجرای coroutines به طور موازی.
  • نحوه اجرای coroutines به ترتیب.
  • چگونه می توان از مدل های موجود coroutines جدید ساخت.
  • چگونه می توان با استفاده از CustomYieldInstruction ، coroutines سفارشی ساخت.

//This is itself a coroutine

IEnumerator RunInSequence()

{

yield return StartCoroutine(Coroutine1());

yield return StartCoroutine(Coroutine2());

}

public void RunInParallel()

{

StartCoroutine(Coroutine1());

StartCoroutine(Coroutine1());

}

Coroutine WaitASecond()

{

return new WaitForSeconds(1);

}

۱۶- از متد های داخلی برای کار با کامپوننت های مشترک یک رابط استفاده کنید. (ظاهرا GetComponent و غیره اکنون نیز برای رابط کار می کنند ، و این نکته را زائد می کنند). بعضی اوقات راحت است که کامپوننت هایی تهیه کنید که یک رابط خاص را اجرا می کنند یا اشیایی را با چنین کامپوننت هایی پیدا می کنند.

پیاده سازی های زیر از typeof به جای نسخه های عمومی این توابع استفاده می کنند. نسخه های عمومی با رابط کار نمی کنند، اما typeof کار می کند. متد های زیر این را مرتب در متد های عمومی قرار می دهد.

public static TInterface GetInterfaceComponent<TInterface>(this Component thisComponent)

where TInterface : class

{

return thisComponent.GetComponent(typeof(TInterface)) as TInterface;

}

۱۷- از متد های داخلی برای راحت تر ساختن نحو استفاده کنید. مثلا:

public static class TransformExtensions

{

public static void SetX(this Transform transform, float x)

{

Vector3 newPosition =

new Vector3(x, transform.position.y, transform.position.z);

transform.position = newPosition;

}

}

۱۸- از گزینه دفاعی GetComponent استفاده کنید. گاهی اوقات اجبار وابستگی به کامپوننت ها از طریق RequiredComponent همیشه امکان پذیر یا مطلوب نیست، مخصوصاً وقتی که GetComponent را از کلاس شخص دیگری فراخوانی می کنید. حتی وقتی که از RequiredComponent استفاده می کنید، خوب است که در کد نشان دهید مولفه را از کجا دریافت می کنید و انتظار دارید که در آن وجود داشته باشد در غیر اینصورت یک خطای راه اندازی است. برای این کار، از یک متد داخلی استفاده کنید که پیام خطا را چاپ می کند یا در صورت یافت نشدن یک استثنای مفید تر نشان می دهد، مانند این:

public static T GetRequiredComponent(this GameObject obj) where T : MonoBehaviour

{

T component = obj.GetComponent();

if(component == null)

{

Debug.LogError(“Expected to find component of type “

+ typeof(T) + ” but found none”, obj);

}

return component;

}

۱۹- از استفاده از اصطلاحات مختلف برای انجام کار یکسان خودداری کنید. در بسیاری از موارد بیش از یک روش اصطلاحی برای انجام کار وجود دارد. در چنین مواردی، یکی را برای استفاده در کل پروژه انتخاب کنید. به همین دلیل است که:

  • برخی اصطلاحات با هم خوب کار نمی کنند. استفاده از یک اصطلاح، طراحی را در یک جهت مجبور می کند که برای اصطلاح دیگر مناسب نیست.
  • استفاده از اصطلاحات مشابه در کل باعث می شود اعضای تیم درک کنند که چه خبر است. این درک ساختار و کد را آسان می کند و باعث می شود اشتباهات کمتر اتفاق بیفتد.

نمونه هایی از گروه های اصطلاحات:

  • Coroutines در مقابل state machines (ماشین حالت).
  • Prefab های تو در تو در مقابل Prefab های پیوندی.
  • استراتژی های جداسازی داده ها.
  • روش های استفاده از sprites برای حالت ها در بازی های دو بعدی.
  • ساختار Prefab
  • استراتژی های Spawn
  • راه های تعیین مکان اشیا: براساس نوع در مقابل نام در مقابل برچسب در مقابل لایه در مقابل مرجع (“پیوندها”).
  • راه های گروه بندی اشیا: براساس نوع در مقابل نام در مقابل برچسب در مقابل لایه در مقابل آرایه های مراجع (“پیوندها”).
  • روش های فراخوانی متد ها روی سایر مولفه ها.
  • یافتن گروه هایی از اشیا در مقابل ثبت نام خودکار.
  • کنترل دستور اجرا (استفاده از راه اندازی دستور اجرای Unity در مقابل منطق yield در مقابل Awake / Start و Update / Late Update Reliance در مقابل متد های دستی در مقابل معماری هر نظم).
  • انتخاب اشیا / موقعیت ها / اهداف با موس در بازی: مدیریت انتخاب در مقابل خود مدیریتی محلی.
  • نگهداری داده ها بین تغییرات صحنه: از طریق PlayerPrefs یا اشیایی که هنگام بارگیری صحنه جدید تخریب نمی شوند.
  • راه های ترکیب (ترکیب ، اضافه کردن و لایه بندی) انیمیشن.
  • مدیریت ورودی (مرکزی در مقابل محلی)

۲۰- کلاس time خود را حفظ کنید تا مکث راحت تر شود. Wrap Time.DeltaTime و Time.TimeSinceLevelLoad را برای حساب کردن مکث و مقیاس زمانی در نظر بگیرید. استفاده از آن نیاز به تنظیمات دارد، اما کار را بسیار ساده تر می کند، به خصوص هنگام اجرای کارها در ساعت های مختلف (مانند انیمیشن های رابط و انیمیشن های بازی).

نکته: یونیتی از unscaledTime و unscaledDeltaTime پشتیبانی می کند، که باعث می شود کلاس Time خود را در بسیاری از شرایط زائد کنید. اما وقتی مقیاس گذاری زمان جهانی روی مولفه هایی که به روش های ناخواسته ننوشته اید تأثیر بگذارد، هنوز هم می تواند مفید باشد.

۲۱- کلاس های سفارشی که نیاز به به روزرسانی دارند نباید به زمان ثابت جهانی دسترسی داشته باشند. در عوض، آن ها باید زمان دلتا را به عنوان یک پارامتر از متد Update خود اختصاص دهند. این باعث می شود که این کلاس ها هنگام استفاده از سیستم مکث همانطور که در بالا توضیح داده شد، یا هنگامی که می خواهید رفتار کلاس سفارشی را سرعت یا کاهش دهید، قابل استفاده باشند.

۲۲- از یک ساختار مشترک برای فراخوانی WWW استفاده کنید. در بازی هایی که ارتباطات سرور زیادی دارند، ده ها فراخوانی WWW معمول است. چه از کلاس WWW یونیتی یا پلاگین استفاده کنید، می توانید از نوشتن یک لایه نازک در صفحه بهره مند شوید.

ما معمولاً یک متد Call (برای هر Get و Post) ، یک Coroutine CallImpl و یک MakeHandler تعریف می کنیم. اساساً، متد Call با استفاده از متد MakeHandler یک super handler از یک پارسر ایجاد می کند. همچنین CallImpl courutine را فراخوانی می کند، که یک URL ایجاد می کند، تماس را برقرار می کند، صبر می کند تا تمام شود و سپس super handler را فراخوانی می کند.

public void Call<T>(string call, Func<string, T> parser, Action<T> onSuccess, Action<string> onFailure)

{

var handler = MakeHandler(parser, onSuccess, onFailure);

StartCoroutine(CallImpl(call, handler));

}

public IEnumerator CallImpl<T>(string call, Action<T> handler)

{

var www = new WWW(call);

yield return www;

handler(www);

}

public Action<WWW> MakeHandler<T>(Func<string, T> parser, Action<T> onSuccess, Action<string> onFailure)

{

return (WWW www) =>

{

if(NoError(www))

   {

   var parsedResult = parser(www.text);

   onSuccess(parsedResult);

   }

   else

   {

   onFailure(“error text”);

   }

   }

}

این کار مزایای مختلفی دارد.

  • به شما این امکان را می دهد که از نوشتن تعداد زیادی کد خودداری کنید.
  • به شما امکان می دهد موارد خاص (مانند نمایش یک مولفه UI در حال بارگذاری یا مدیریت برخی خطاهای عمومی) را در یک مکان مرکزی مدیریت کنید.

۲۳- اگر متن زیادی دارید، آن را در یک فایل قرار دهید. آن را در فیلد های ویرایش در بازرس قرار ندهید. بدون نیاز به باز کردن ویرایشگر یونیتی و به ویژه بدون نیاز به ذخیره صحنه، تغییر آن را آسان کنید.

۲۴- اگر می خواهید بومی سازی کنید، تمام رشته های خود را به یک مکان جدا کنید. خیلی راه ها برای انجام دادن این کار وجود دارد. یک روش این است که برای هر رشته، کلاس Text با یک public string تعریف کنید، به عنوان مثال پیش فرض ها روی انگلیسی تنظیم شده است. زبان های دیگر این را زیر کلاس می کنند و فیلد ها را با معادل های زبان دوباره تنظیم می کنند.

تکنیک های پیچیده تر (مناسب وقتی متن زیاد باشد و یا تعداد زبان ها زیاد باشد) در یک صفحه گسترده خوانده می شوند و منطقی برای انتخاب رشته مناسب بر اساس زبان انتخاب شده ارائه می دهند.

دانلود رایگان ۳ تا از بهترین جزوه های PDF آموزش یونیتی

طراحی کلاس

۲۵- در مورد نحوه اجرای فیلد های قابل بررسی تصمیم بگیرید و آن را به عنوان یک استاندارد در آورید. دو راه وجود دارد: فیلد ها را public کنید یا private کنید و به عنوان [SerializeField] علامت گذاری کنید. مورد اخیر “صحیح تر” است اما راحت تر نیست (و مطمئناً روشی نیست که توسط خود یونیتی رواج یابد). هر راهی را که انتخاب می کنید، آن را به یک استاندارد تبدیل کنید تا توسعه دهندگان در تیم شما بدانند چگونه یک فیلد عمومی را تفسیر کنند.

  • فیلد های قابل بازرسی، public هستند. در این سناریو، public به معنای “تغییر متغیر توسط طراح در زمان اجرا ایمن است. از تنظیم مقدار آن در کد خودداری کنید”.
  • فیلد های قابل بازرسی private هستند و با قابلیت Serializable مشخص می شوند. در این سناریو، public به معنای “تغییر این متغیر در کد امن است” (و از این رو شما نباید تعداد زیادی را مشاهده کنید و نباید هیچ فیلد عمومی در MonoBehaviours و ScriptableObjects وجود داشته باشد).

۲۶- برای مولفه ها، هرگز متغیرهایی را که نباید در بازرس اصلاح شود، public کنید. در غیر این صورت توسط یک طراح تغییر داده می شود، به خصوص اگر مشخص نباشد که چه کاری انجام می دهند. در برخی موارد نادر اجتناب ناپذیر است (به عنوان مثال، هنگامی که برخی از اسکریپت های ویرایشگر نیاز به کنترل آن دارند). در این صورت می توانید از ویژگی HideInInspector برای مخفی کردن آن در بازرس استفاده کنید.

۲۷- از property drawers استفاده کنید تا فیلد ها کاربر پسندتر شوند. از property drawers می توان برای شخصی سازی کنترل ها در بازرس استفاده کرد. این به شما امکان می دهد کنترل هایی متناسب با ماهیت داده ها ایجاد کنید و محافظ های امنیتی خاصی را در محل قرار دهید (مانند محدود کردن دامنه متغیرها). از ویژگی Header برای سازماندهی فیلد ها استفاده کنید و از ویژگی Tooltip برای ارائه مستندات اضافی به طراحان استفاده کنید.

۲۸- property drawers را بر ویرایشگرهای سفارشی ترجیح دهید. property drawers در هر نوع فیلد اجرا می شوند و بنابراین کار کمتری برای پیاده سازی دارند. همچنین قابلیت استفاده مجدد بیشتری دارند – هرگاه برای یک نوع پیاده سازی شوند، می توانند برای آن نوع در هر کلاسی استفاده شوند. ویرایشگرهای سفارشی با استفاده از MonoBehaviour اجرا می شوند و بنابراین کمتر قابل استفاده هستند و کار بیشتری دارند.

۲۹- به صورت پیش فرض MonoBehaviours را مهر و موم کنید. به طور کلی، MonoBehaviour های یونیتی قابل ارث بری نیستند:

  • روشی که یونیتی متد های پیام مانند Start و Update را فرا می خواند کار با این متد ها را در زیر کلاس ها مشکل می کند. اگر مراقب نباشید، مورد اشتباهی فراخوانی می شود یا فراموش می کنید که یک متد پایه را فرا بخوانید.
  • هنگامی که از ویرایشگرهای سفارشی استفاده می کنید، معمولاً باید سلسله مراتب وراثت را برای ویرایشگران کپی کنید. هر کسی که می خواهد یکی از کلاس های شما را گسترش دهد، باید ویرایشگر خود را ارائه دهد یا با هر چیزی که شما فراهم کردید کارش را انجام دهد.

در مواردی که وراثت فراخوانی می شود، هیچ یک از متد های پیام یونیتی را ارائه ندهید و اگر می توانید از آن جلوگیری کنید. اگر چنین کردید، آن ها را مجازی نکنید. در صورت لزوم می توانید یک تابع مجازی خالی تعریف کنید که از متد پیام فراخوانی شود که یک کلاس فرزند می تواند برای انجام کارهای اضافی override کند.

public class MyBaseClass

{

public sealed void Update()

{

CustomUpdate();

… // This class’s update

}

//Called before this class does its own update

//Override to hook in your own update code.

virtual public void CustomUpdate(){};

}

public class Child : MyBaseClass

{

override public void CustomUpdate()

{

//Do custom stuff

}

}

این باعث می شود که یک کلاس به طور تصادفی کد شما را override نکند، اما با این وجود این قابلیت را دارد که با پیام های یونیتی ارتباط برقرار کند. یک دلیل که این الگو را دوست نداریم این است که ترتیب کارها مشکل ساز می شود. در مثال بالا ممکن است فرزند بخواهد پس از اینکه کلاس به روزرسانی خود را انجام داد، مستقیماً کارهایی را انجام دهد.

۳۰- رابط کاربری را از منطق بازی جدا کنید. اجزای رابط به طور کلی نباید در مورد بازی مورد استفاده آن ها چیزی بدانند. داده هایی را که برای مصور سازی نیاز دارند به آن ها بدهید و در رویدادها مشترک شوید تا بفهمید کاربر چه تعاملی با آن ها دارد. اجزای رابط نباید gamelogic یا منطق بازی را انجام دهند. آن ها می توانند ورودی را برای اطمینان از معتبر بودن آن فیلتر کنند، اما پردازش قانون اصلی باید در جای دیگری انجام شود. در بسیاری از بازی های پازلی، قطعات گسترش دهنده رابط هستند و نباید حاوی قوانینی باشند. (به عنوان مثال، یک مهره شطرنج نباید حرکات قانونی خود را محاسبه کند.)

به همین ترتیب، ورودی باید از منطقی که بر آن ورودی عمل می کند جدا شود. از یک کنترل کننده ورودی استفاده کنید که بازیگر شما را از قصد حرکت مطلع کند. بازیگر مسئولیت اینکه آیا واقعاً حرکت کند را انجام می دهد.

در اینجا مثالی از مولفه UI برداشته شده است که به کاربر اجازه می دهد اسلحه ای را از لیست انتخاب ها انتخاب کند. تنها چیزی که این کلاس ها در مورد بازی می دانند کلاس Weapon است (و فقط به این دلیل که Weapon منبع مفیدی برای داده هایی است که این container برای نمایش نیاز دارد). این بازی همچنین چیزی در مورد container نمی داند. تنها کاری که باید انجام دهد ثبت نام در رویداد OnWeaponSelect است.

public WeaponSelector : MonoBehaviour

{

public event Action OnWeaponSelect {add; remove; }

//the GameManager can register for this event

public void OnInit(List weapons)

{

foreach(var weapon in weapons)

{

var button = … //Instantiates a child button and add it to the hierarchy

buttonOnInit(weapon, () => OnSelect(weapon));

// child button displays the option,

// and sends a click-back to this component

}

}

public void OnSelect(Weapon weapon)

{

if(OnWepaonSelect != null) OnWeponSelect(weapon);

}

}

public class WeaponButton : MonoBehaviour

{

private Action<> onClick;

public void OnInit(Weapon weapon, Action onClick)

{

… //set the sprite and text from weapon

this.onClick = onClick;

}

public void OnClick() //Link this method in as the OnClick of the UI Button component

{

Assert.IsTrue(onClick != null); //Should not happen

onClick();

}

}

۳۱- پیکربندی، حالت و دفترداری جداگانه.

متغیرهای پیکربندی یا configuration متغیرهایی هستند که در بازرس تنظیم می شوند تا شی شما را از طریق خصوصیات آن تعریف کنند. به عنوان مثال، maxHealth.

متغیرهای حالت یا state متغیرهایی هستند که وضعیت فعلی شی شما را کاملاً تعیین می کنند و متغیرهایی هستند که باید در صورت پشتیبانی بازی از ذخیره سازی، ذخیره کنید. به عنوان مثال، currentHealth.

متغیرهای دفترداری یا bookkeeping برای سرعت، راحتی یا حالات انتقالی استفاده می شوند. آن ها همیشه از متغیرهای حالت تعیین می شوند. به عنوان مثال، previousHealth.

با جدا کردن این نوع متغیرها، دانستن اینکه چه چیزی را می توانید تغییر دهید، چه چیزی را باید ذخیره کنید، چه چیزی را برای ارسال/ بازیابی از طریق شبکه نیاز دارید، آسان تر می شود و به شما امکان می دهد تا حدی این کار را انجام دهید. در اینجا یک مثال ساده با این تنظیم وجود دارد.

public class Player

{

[Serializable]

public class PlayerConfigurationData

{

public float maxHealth;

}

[Serializable]

public class PlayerStateData

{

public float health;

}

public PlayerConfigurationData configuration;

private PlayerState stateData;

//book keeping

private float previousHealth;

public float Health

{

public get { return stateData.health; }

private set { stateData.health = value; }

}

}

۳۲- از استفاده از آرایه های همراه با شاخص public خودداری کنید. به عنوان مثال، آرایه ای از سلاح ها، آرایه ای از گلوله ها و آرایه ای از ذرات را تعریف نکنید تا کد شما به این شکل باشد:

public void SelectWeapon(int index)

{

currentWeaponIndex = index;

Player.SwitchWeapon(weapons[currentWeapon]);

}

public void Shoot()

{

Fire(bullets[currentWeapon]);

FireParticles(particles[currentWeapon]);

}

مشکل این مسئله این است که زیاد در کد نیست، بلکه تنظیم آن در بازرس و بدون اشتباه است.

در عوض، یک کلاس تعریف کنید که سه متغیر را محصور کند و یک آرایه از آن ایجاد کنید:

[Serializable]

public class Weapon

{

public GameObject prefab;

public ParticleSystem particles;

public Bullet bullet;

}

کد شسته و رفته به نظر می رسد، اما مهم تر از همه، اشتباه کردن در تنظیم داده ها در بازرس دشوارتر است.

۳۳- از استفاده از آرایه ها برای ساختار غیر از توالی ها خودداری کنید. به عنوان مثال، یک بازیکن ممکن است سه نوع حمله داشته باشد. هر کدام از سلاح های فعلی استفاده می کنند، اما گلوله های مختلف و رفتارهای مختلفی تولید می کنند.

ممکن است وسوسه شوید که سه گلوله را در یک آرایه ریخته و سپس از این نوع منطق استفاده کنید:

public void FireAttack()

{

/// behaviour

Fire(bullets[0]);

}

public void IceAttack()

{

/// behaviour

Fire(bullets[1]);

}

public void WindAttack()

{

/// behaviour

Fire(bullets[2]);

}

Enums can make things look better in code…

public void WindAttack()

{

/// behaviour

Fire(bullets[WeaponType.Wind]);

}

… اما در بازرس نیست.

بهتر است از متغیرهای جداگانه استفاده کنید تا نام ها به شما نشان دهند که کدام محتوا را قرار دهید. از یک کلاس برای آراستن آن استفاده کنید.

[Serializable]

public class Bullets

{

public Bullet fireBullet;

public Bullet iceBullet;

public Bullet windBullet;

}

این فرض می کند که هیچ داده دیگری از آتش، یخ و باد وجود ندارد.

۳۴- داده ها را در کلاس های قابل سریال سازی گروه بندی کنید تا کارها را بهتر در بازرس انجام دهید. برخی از موجودیت ها ممکن است ده ها مورد قابل تغییر داشته باشند. یافتن متغیر مناسب در بازرس می تواند به یک کابوس تبدیل شود. برای سهولت کار، این مراحل را دنبال کنید:

  • برای گروه های متغیر کلاس های جداگانه تعریف کنید. آن ها را عمومی و قابل سریال سازی کنید.
  • در کلاس اصلی، متغیرهای عمومی از هر نوع را که در بالا تعریف شده تعریف کنید.
  • این متغیرها را در ابتدا یا شروع مقداردهی اولیه نکنید. از آنجا که آن ها قابل سریال سازی هستند، یونیتی از آن مراقبت خواهد کرد.
  • با تعیین مقادیر در تعریف می توانید پیش فرض ها را مانند قبل تعیین کنید.

این کار متغیرها را در واحدهای جمع شونده در بازرس گروه بندی می کند که مدیریت آن آسان تر است.

[Serializable]

public class MovementProperties //Not a MonoBehaviour!

{

public float movementSpeed;

public float turnSpeed = 1; //default provided

}

public class HealthProperties //Not a MonoBehaviour!

{

public float maxHealth;

public float regenerationRate;

}

public class Player : MonoBehaviour

{

public MovementProperties movementProeprties;

public HealthPorperties healthProeprties;

}

۳۵- کلاس هایی را بسازید که MonoBehaviours Serializable نیست حتی اگر در فیلد های public استفاده نشود. این به شما اجازه می دهد تا فیلد های کلاس را در بازرس، وقتی بازرس در حالت اشکال زدایی است مشاهده کنید. این برای کلاس های تو در تو (private یا public) نیز کاربرد دارد.

۳۶- از ایجاد تغییر در بازرس قابل تغییر در کد خودداری کنید. متغیری که در بازرس قابل تغییر است، یک متغیر پیکربندی است و باید به عنوان یک مقدار ثابت run time با آن رفتار شود و نه به عنوان یک متغیر حالت. پیروی از این عمل نوشتن متد های تنظیم مجدد حالت یک مولفه به حالت اولیه را آسان تر می کند و کار متغیر را واضح تر می کند.

public class Actor : MonoBehaviour

{

public float initialHealth = 100;

private float currentHealth;

public void Start()

{

ResetState();

}

private void Respawn()

{

ResetState();

}

private void ResetState()

{

currentHealth = initialHealth;

}

}

الگوها

الگوها روش هایی برای حل مشکلات مکرر به روشی استاندارد هستند. کتاب Bob Nystrom Game Game Programming Patterns منبع مفیدی برای دیدن چگونگی اعمال الگوها بر روی مشکلات پیش آمده در برنامه نویسی بازی است. یونیتی به خودی خود از بسیاری از این الگوها استفاده می کند: Instantiate مثالی از الگوی نمونه اولیه است. MonoBehaviours نسخه ای از الگوی template را دنبال می کند، UI و انیمیشن از الگوی observer استفاده می کنند و موتور انیمیشن جدید از ماشین های state استفاده می کند.

این نکات مربوط به استفاده از الگوهایی به طور خاص با یونیتی است.

۳۷- برای راحتی از singleton استفاده کنید. کلاس زیر هر کلاسی را که از آن ارث ببرد به صورت خودکار singleton می کند:

public class Singleton<T> : MonoBehaviour where T : MonoBehaviour

{

protected static T instance;

//Returns the instance of this singleton.

public static T Instance

{

get

{

if(instance == null)

{

instance = (T) FindObjectOfType(typeof(T));

if (instance == null)

{

Debug.LogError(“An instance of ” + typeof(T) +

” is needed in the scene, but there is none.”);

}

}

return instance;

}

}

}

Singletons برای مدیران مانند ParticleManager یا AudioManager یا GUIManager مفید است.

(بسیاری از برنامه نویسان نسبت به کلاس های مبهم با نام XManager هشدار می دهند زیرا این کلاس به یک کلاس نامناسب اشاره می کند، یا با کارهای غیر مرتبط زیادی طراحی شده اند. به طور کلی ما با این موافقیم. با این حال در هر بازی تعداد کمی مدیر داریم و آن ها در هر بازی همین کار را انجام می دهند، به طوری که این کلاس ها در واقع اصطلاحات هستند.)

  • از موارد singleton برای نمونه های منحصر به فرد Prefab که مدیر نیستند (مانند Player) خودداری کنید. رعایت نکردن این اصل سلسله مراتب وراثت را پیچیده کرده و انواع خاصی از تغییرات را دشوارتر می کند. در عوض در GameManager به این موارد مراجعه کنید.
  • خصوصیات و متد های static را برای متغیرها و متد های public تعریف کنید که اغلب از خارج کلاس استفاده می شوند. با این کار می توانید GameManager.Player را به جای GameManager.Instance.player بنویسید.

همانطور که در نکات دیگر توضیح داده شد، singleton ها برای ایجاد نقاط Spawn پیش فرض و اشیایی که بین لود های صحنه وجود دارند و داده های سراسری را ردیابی می کنند نیز مفید هستند.

۳۸- از ماشین های حالت برای بدست آوردن رفتار متفاوت در حالت های مختلف یا اجرای کد در انتقال های حالت استفاده کنید. یک ماشین حالت سبک وزن دارای تعدادی حالت است و برای هر حالت به شما امکان می دهد عملکردهایی را که باید هنگام ورود یا وجود حالت اجرا کنید، و یک اقدام به روزرسانی را تعیین کنید. با این کار کد تمیزتر می شود و خطای آن کمتر است. نشانه خوبی که می توانید از یک ماشین حالت بهره مند شوید این است که کد متد Update شما دارای عبارت if یا switch است که عملکرد آن را تغییر می دهد یا متغیرهایی مانند hasShownGameOverMessage.

public void Update()

{

if(health <= 0)

{

if(!hasShownGameOverMessage)

{

ShowGameOverMessage();

hasShownGameOverMessage = true; //Respawning resets this to false

}

}

else

{

HandleInput();

}

}

با حالت های بیشتر ، این نوع کد می تواند بسیار به هم ریخته شود. یک ماشین حالت می تواند آن را بسیار تمیزتر کند.

۳۹- از فیلد های نوع UnityEvent برای تنظیم الگوی مشاهده در بازرس استفاده کنید. کلاس UnityEvent به شما امکان می دهد متدهایی را که حداکثر چهار پارامتر در بازرس دارند با استفاده از همان رابط کاربری UI به عنوان رویدادهای موجود در دکمه ها پیوند دهید. این امر به ویژه برای مقابله با ورودی، بسیار مفید است.

۴۰- از الگوی observer برای تشخیص زمان تغییر مقدار فیلد استفاده کنید. مشکل اجرای کد فقط زمانی است که یک متغیر به طور مکرر بازی ها را تغییر می دهد. ما یک راه حل کلی از این موضوع را در یک کلاس عمومی ایجاد کرده ایم که به شما امکان می دهد هر زمان مقدار تغییر کرد، برای رویدادها منطبق کنید. در اینجا مثالی با health آورده شده است. در اینجا نحوه ساخت آن آمده است:

/*ObservedValue*/ health = new ObservedValue(100);

health.OnValueChanged += () => { if(health.Value <= 0) Die(); };

اکنون می توانید آن را در همه جا تغییر دهید، بدون اینکه در هر مکانی که آن را چک کنید، به عنوان مثال:

if(hit) health.Value -= 10;

هر زمان که health به نقطه زیر صفر رسید، متد Die فراخوانی می شود.

۴۱- از الگوی actor در Prefabs استفاده کنید. (این یک الگوی “استاندارد” نیست.)

یک actor مولفه اصلی در Prefab است. معمولاً مولفه ای که ” identity” Prefab را فراهم می کند و کد سطح بالاتر اغلب با آن ارتباط برقرار می کند. actor برای انجام کارهای خود از مولفه های دیگر روی همان شی (و گاهی اوقات روی فرزندان) استفاده می کند.

اگر از منو یک شی دکمه ای ایجاد کنید، یک شی بازی با مولفه Sprite و Button (و یک فرزند با مولفه Text) ایجاد می کند. در این حالت، دکمه مولفه actor است. به طور مشابه، دوربین اصلی به طور معمول دارای چندین مولفه (GUI Layer، Flare Layer، Audio Listener) علاوه بر مولفه Camera ضمیمه شده است. دوربین actor است.

ممکن است یک actor برای کارکرد صحیح به سایر مولفه ها نیز نیاز داشته باشد. با استفاده از ویژگی های زیر در مولفه actor خود می توانید Prefab خود را مقاوم و مفیدتر کنید:

  • از RequiredComponent برای نشان دادن تمام مولفه هایی که actor شما به یک شی بازی نیاز دارد استفاده کنید. (پس از آن actor شما همیشه می تواند با خیال راحت GetComponent را فراخوانی کند، بدون اینکه نیازی به بررسی اینکه مقدار برگشتی خالی است، داشته باشید.)
  • از DisallowMultipleComponent برای جلوگیری از چندین مورد از یک مولفه پیوست شده استفاده کنید. پس از آن actor شما می تواند همیشه GetComponent را فرا بخواند بدون اینکه نگران باشید که وقتی بیش از یک مولفه به آن متصل است، رفتار چگونه خواهد بود).
  • اگر شی actor شما فرزند دارد از SelectionBase استفاده کنید. با این کار انتخاب در نمای صحنه راحت تر می شود.

[RequiredComponent(typeof(HelperComponent))]

[DisallowMultipleComponent]

[SelectionBase]

public class Actor : MonoBehaviour

{

…//

}

۴۲- از ژنراتورها برای جریان داده های تصادفی و الگویی استفاده کنید. (این یک الگوی استاندارد نیست، اما به نظر ما بسیار مفید است.)

یک ژنراتور مشابه ژنراتور تصادفی است: یک شی با متد Next است که می توان آن را فراخوانی کرد تا مورد جدیدی از نوع خاص را بدست آورد. ژنراتورها را می توان در حین ساخت دستکاری کرد تا تنوع زیادی از الگوها یا انواع مختلف تصادفی را تولید کند. آن ها مفید هستند، زیرا منطق تولید مورد جدید را جدا از مکان مورد نیاز نگه می دارند، بنابراین کد شما بسیار تمیزتر است.

در اینجا چند نمونه برایتان آورده شده است:

var generator = Generator

.RamdomUniformInt(500)

.Select(x => 2*x); //Generates random even numbers between 0 and 998

var generator = Generator

.RandomUniformInt(1000)

.Where(n => n % 2 == 0); //Same as above

var generator = Generator

.Iterate(0, 0, (m, n) => m + n); //Fibonacci numbers

var generator = Generator

.RandomUniformInt(2)

.Select(n => 2*n – 1)

.Aggregate((m, n) => m + n); //Random walk using steps of 1 or -1 one randomly

var generator = Generator

.Iterate(0, Generator.RandomUniformInt(4), (m, n) => m + n – 1)

.Where(n >= 0); //A random sequence that increases on average

ما برای تولید موانع spawn، تغییر رنگ پس زمینه، موسیقی رویه ای، ایجاد توالی حروف که در بازی های کلمات ایجاد می کنند و موارد دیگر از ژنراتور ها استفاده می کنیم. ژنراتورها همچنین برای کنترل برنامه های مشترکی که در فواصل غیر ثابت تکرار می شوند، به خوبی کار می کنند، با استفاده از ساختار:

while (true)

{

//Do stuff

yield return new WaitForSeconds(timeIntervalGenerator.Next());

}

Prefabs و اشیا Scriptable

۴۳- برای همه موارد از Prefab استفاده کنید. تنها اشیا بازی در صحنه شما نباید Prefab (یا بخشی از Prefabs) باشند باید پوشه باشند. حتی اشیا unique که فقط یک بار استفاده می شوند باید Prefab باشند. این کار باعث می شود ایجاد تغییراتی که نیازی به تغییر صحنه ندارند آسان تر باشد.

۴۴- Prefabs را به prefabs پیوند دهید؛ instances را به instances پیوند ندهید. هنگام انداختن Prefab در صحنه، پیوندها به Prefabs حفظ می شود. پیوند به instances نیست. پیوند با Prefabs در هر زمان ممکن تنظیم صحنه را کاهش می دهد و نیاز به تغییر صحنه ها را کاهش می دهد.

تا آنجا که ممکن است، به طور خودکار بین instance ها پیوند برقرار کنید. اگر می خواهید instance ها را پیوند دهید، پیوندها را از طریق برنامه ریزی ایجاد کنید. به عنوان مثال، Prefab پلیر می تواند هنگام شروع به کار خود را در GameManager ثبت یا register کند، یا GameManager می تواند نمونه Prefab پلیر را هنگام شروع پیدا کند.

۴۵- اگر می خواهید اسکریپت های دیگری اضافه کنید، مش را در ریشه Prefab ها قرار ندهید. وقتی Prefab را از مش درست می کنید، ابتدا مش را به یک شی خالی بازی وصل کنید و آن را ریشه دهید. اسکریپت ها را روی ریشه قرار دهید، نه روی گره مش. بدین ترتیب جایگزینی مش با مش دیگر بدون از دست دادن مقادیری که در بازرس تنظیم کرده اید، بسیار آسان تر است.

۴۶- ​​به جای Prefab، از اشیا قابل نوشتن (scriptable ) برای داده های پیکربندی مشترک استفاده کنید.

اگر این کار را انجام دهید:

  • صحنه ها کوچکتر هستند
  • شما به اشتباه نمی توانید در یک صحنه (در یک نمونه Prefab) تغییراتی ایجاد کنید.

۴۷- از اشیا قابل نوشتن برای داده های سطح استفاده کنید. داده های سطح اغلب در XML یا JSON ذخیره می شوند، اما در عوض استفاده از اشیا قابل نوشتن دارای چند مزیت است:

  • در ویرایشگر قابل ویرایش است. این اعتبار سنجی داده ها را آسان تر می کند و برای طراحان غیر فنی دوستانه تر است. علاوه بر این می توانید از ویرایشگرهای سفارشی استفاده کنید تا ویرایش آن حتی ساده تر شود.
  • لازم نیست نگران خواندن / نوشتن و تجزیه داده ها باشید.
  • تقسیم و لانه سازی و مدیریت دارایی های حاصل از آن آسان تر است و بنابراین می توان سطح آن را از بلوک های ساختمانی (building blocks) ساخت تا یک پیکربندی گسترده.

۴۸- از Scriptable Objects برای پیکربندی رفتار در بازرس استفاده کنید. اشیا قابل نوشتن معمولاً با پیکربندی داده ها مرتبط هستند، اما به شما این امکان را می دهند که از “متد ها” به عنوان داده استفاده کنید.

یک سناریو را در نظر بگیرید که در آن نوع Enemy دارید و هر دشمن یک دسته از SuperPowers را دارد. اگر این کلاس ها در کلاس Enemy باشد می توانید این کلاس های عادی را بسازید و لیستی داشته باشید. اما بدون ویرایشگر سفارشی نمی توانید لیستی از SuperPowers های مختلف (هرکدام با ویژگی های خاص خود) را در بازرس تنظیم کنید. اما اگر این SuperPowers ها را دارایی کنید (آن ها را به عنوان ScriptableObjects پیاده سازی کنید)، می توانید!

نحوه به نظر رسیدن آن در اینجا است:

public class Enemy : MonoBehaviour

{

public SuperPower superPowers;

public UseRandomPower()

{

superPowers.RandomItem().UsePower(this);

}

}

public class BasePower : ScriptableObject

{

virtual void UsePower(Enemy self)

{

}

}

[CreateAssetMenu(“BlowFire”, “Blow Fire”)

public class BlowFire : SuperPower

{

public strength;

override public void UsePower(Enemy self)

{

///program blowing fire here

}

}

چند نکته وجود دارد که باید هنگام رعایت این الگو از آن ها آگاه باشید:

  • به طور قابل اعتماد نمی توان Scriptable Object را انتزاعی دانست. در عوض، از کلاس های پایه concrete ‌استفاده کنید و NotImplementedExcepts را در متد هایی که باید انتزاعی باشد، throw کنید. شما همچنین می توانید یک ویژگی Abstract تعریف کنید و کلاس ها و متد هایی را که باید انتزاعی باشند، علامت گذاری کنید.

Scriptable Object که public هستند نمی توانند سریال سازی شوند. با این حال، می توانید از کلاس های پایه generic استفاده کنید و فقط زیر کلاس هایی را که همه موارد generic را مشخص می کند، سریال سازی کنید.

۴۹- برای تخصصی سازی prefab ها از Scriptable Object ها استفاده کنید. اگر پیکربندی دو شی فقط در برخی خصوصیات متفاوت باشد، قرار دادن دو نمونه در صحنه و تنظیم آن خصوصیات بر روی نمونه ها، معمول است. معمولاً بهتر است یک کلاس جداگانه از خصوصیاتی که می توانند بین این دو نوع تفاوت داشته باشند، به یک کلاس Scriptable Object جداگانه بسازید.

این کار به شما انعطاف پذیری بیشتری می دهد:

  • می توانید از ارث بری کلاس specialization خود برای دادن ویژگی های خاص تر به انواع مختلف شی استفاده کنید.
  • تنظیم صحنه بسیار ایمن تر است (شما به جای اینکه همه خصوصیات را برای ساختن شی به نوع دلخواه تنظیم کنید، فقط Scriptable Object مناسب را انتخاب می کنید).
  • دستکاری این اشیا در زمان اجرا از طریق کد آسان تر است.
  • اگر چندین نمونه از این دو نوع داشته باشید، می دانید که هنگام ایجاد تغییرات، ویژگی های آن ها همیشه سازگار خواهند بود.
  • می توانید مجموعه ای از متغیرهای پیکربندی را به مجموعه هایی تقسیم کنید که می توانند با هم ترکیب شوند.

در اینجا یک مثال ساده از این تنظیم وجود دارد.

[CreateAssetMenu(“HealthProperties.asset”, “Health Properties”)]

public class HealthProperties : ScriptableObject

{

public float maxHealth;

public float resotrationRate;

}

public class Actor : MonoBehaviour

{

public HealthProperties healthProperties;

}

اگر تعداد specialization ها زیاد است، ممکن است بخواهید specialization را به عنوان یک کلاس معمولی تعریف کنید و از لیستی از این موارد در یک Scriptable Object استفاده کنید که به مکان مناسبی پیوند داده شده است که می توانید از آن استفاده کنید (مانند GameManager).

public enum ActorType

{

Vampire, Wherewolf

}

[Serializable]

public class HealthProperties

{

public ActorType type;

public float maxHealth;

public float resotrationRate;

}

[CreateAssetMenu(“ActorSpecialization.asset”, “Actor Specialization”)]

public class ActorSpecialization : ScriptableObject

{

public List healthProperties;

public this[ActorType]

{

get { return healthProperties.First(p => p.type == type); } //Unsafe version!

}

}

public class GameManager : Singleton

{

public ActorSpecialization actorSpecialization;

}

public class Actor : MonoBehaviour

{

public ActorType type;

public float health;

//Example usage

public Regenerate()

{

health

+= GameManager.Instance.actorSpecialization[type].resotrationRate;

}

}

۵۰- از ویژگی CreateAssetMenu برای افزودن خودکار ScriptableObject به منوی Asset / Create استفاده کنید.

Debug کردن یا اشکال زدایی

۵۱- بیاموزید که چگونه از امکانات اشکال زدایی یونیتی به طور موثر استفاده کنید.

  • اشیاء زمینه ای را به Debug.Log اضافه کنید تا ببینید از کجا تولید می شوند.
  • از Debug.Break برای مکث بازی در ویرایشگر استفاده کنید (به عنوان مثال اگر بخواهید شرایط خطا رخ دهد و بخواهید ویژگی های مولفه را در آن فریم بررسی کنید، مفید است).
  • برای اشکال زدایی بصری از توابع Debug.DrawRay و Debug.DrawLine استفاده کنید.
  • برای اشکال زدایی بصری از Gizmos استفاده کنید. همچنین می توانید با استفاده از ویژگی DrawGizmo، رندر های gizmo را خارج از رفتارهای مونو تهیه کنید.
  • از نمای بازرس اشکال زدایی استفاده کنید (برای دیدن مقادیر فیلدهای خصوصی در زمان اجرا در یونیتی با استفاده از بازرس).

۵۲- بیاموزید که چگونه از اشکال زدای IDE خود به طور موثر استفاده کنید. به عنوان مثال بازی های Debugging Unity را در Visual Studio مشاهده کنید.

۵۳- از خطایاب تصویری استفاده کنید که نمودار مقادیر را با گذشت زمان ترسیم می کند. این برای رفع اشکال در فیزیک، انیمیشن و سایر فرآیندهای پویا، به ویژه اشکالات پراکنده بسیار مفید است. شما قادر خواهید بود که اشکال موجود در نمودار را مشاهده کنید و همچنین ببینید که متغیرهای دیگر به طور همزمان تغییر می کنند. بازرسی بصری همچنین انواع خاصی از رفتارهای عجیب و غریب را روشن می کند، مانند مقادیری که بیش از حد تغییر می کنند. ما از اجزای مانیتور استفاده می کنیم، اما چندین مورد در دسترس است.

۵۴- از ورود به سیستم کنسول بهبود یافته استفاده کنید. از یک ادیتور افزودنی استفاده کنید که به شما امکان می دهد خروجی کد رنگ را بر اساس دسته بندی ها تنظیم کنید و خروجی را با توجه به آن دسته بندی فیلتر کنید. ما از Editor Console Pro استفاده می کنیم، اما چندین مورد برای این کار موجود است.

۵۵- از ابزارهای آزمون یونیتی به ویژه برای آزمایش الگوریتم ها و کد ریاضی استفاده کنید.

۵۶- از ابزارهای تست یونیتی برای اجرای تست های « scratchpad» استفاده کنید. ابزار آزمون یونیتی نه تنها برای آزمون های رسمی مناسب است. همچنین می توان از آن ها برای آزمایش scratchpad راحت که بدون نیاز به اجرای یک صحنه در ویرایشگر قابل اجرا است استفاده کرد.

۵۷- میانبرهای عکسبرداری از صفحه را پیاده سازی کنید. بسیاری از اشکالات بصری هستند و هنگام تهیه عکس بسیار آسان تر گزارش می شوند. سیستم ایده آل باید یک شمارنده در PlayerPrefs نگهداری کند تا عکس های پی در پی رونویسی نشوند. عکس های صفحه باید در خارج از پوشه پروژه ذخیره شوند.

۵۸- برای چاپ عکس های متغیرهای مهم میانبرها را پیاده سازی کنید. این امر باعث می شود هنگام وقوع اتفاق غیر منتظره در طول بازی که می توانید آن را بازرسی کنید، ورود برخی از اطلاعات آسان باشد. البته این که کدام متغیرها، به بازی بستگی دارد. شما با اشکالات معمولی که در بازی رخ می دهد هدایت خواهید شد. به عنوان مثال می توان به موقعیت های بازیکن و دشمنان، یا “حالت تفکر” یک بازیکن هوش مصنوعی اشاره کرد (مانند مسیری که می خواهد دنبال کند).

۵۹- گزینه های اشکال زدایی را برای سهولت انجام آزمایش انجام دهید. چند نمونه:

  • قفل همه موارد را باز کنید.
  • دشمنان را از کار بیندازید.
  • GUI را غیرفعال کنید.
  • بازیکن را شکست ناپذیر کنید.
  • همه گیم پلی بازی را غیرفعال کنید.

مراقب باشید به طور تصادفی گزینه های رفع اشکال را مرتکب نشوید. تغییر گزینه های اشکال زدایی می تواند از توسعه دهندگان دیگر در تیم شما پنهان بماند.

۶۰- ثابت های مربوط به کلیدهای میانبر اشکال زدایی را تعریف کنید و آن ها را در یک مکان نگه دارید. کلیدهای اشکال زدایی معمولاً (یا به راحتی) مانند بقیه ورودی بازی در یک مکان واحد پردازش نمی شوند. برای جلوگیری از برخورد کلیدهای میانبر، ثابت ها را در یک مکان مرکزی تعریف کنید. یک گزینه جایگزین پردازش همه کلیدها در یک مکان است بدون توجه به اینکه عملکرد رفع اشکال است یا خیر. (نکته منفی این است که این کلاس فقط برای این ممکن است به رفرنس های اضافی به اشیا نیاز داشته باشد)

۶۱- هنگام تولید مش رویه، کره های کوچک را در راس، رسم یا spawnکنید. این به شما کمک می کند قبل از شروع شلوغ کاری با مثلث ها و UV ها که باعث می شود شبکه شما نمایش داده شود، مطمئن شوید که رأس های شما در محلی که قرار است باشند، هستند و اندازه مش مناسب است.

کارایی

۶۲- به دلایل عملکرد، مراقب توصیه های عمومی در مورد طراحی و ساخت باشید.

  • چنین توصیه هایی غالباً مبتنی بر افسانه ها است و آزمون نیز تأیید نمی کند.
  • گاهی اوقات توصیه ها با آزمایشات پشتیبانی می شوند اما آزمایشات معیوب هستند.
  • گاهی اوقات توصیه ها با آزمون های صحیح پشتیبانی می شوند، اما آن ها در یک زمینه غیر واقعی یا متفاوت هستند. (به عنوان مثال، نشان دادن چگونگی استفاده از آرایه ها سریعتر از لیست های عمومی هستند، آسان است. با این حال، در زمینه یک بازی واقعی، این تفاوت تقریباً همیشه ناچیز است. به همین ترتیب، اگر آزمایشات روی سخت افزارهای متفاوت از دستگاه های هدف شما اعمال شود، نتایج آن ها ممکن است برای شما معنی دار نباشد.)
  • گاهی اوقات توصیه صحیح است، اما قدیمی است.
  • گاهی اوقات مشاوره اعمال می شود. با این حال، یک معامله وجود دارد. بازی های آهسته که گاهی اوقات حمل می شوند بهتر از بازی های سریعی هستند که این کار را نمی کنند. و بازی های به شدت بهینه سازی شده احتمالاً حاوی کد حیله گر هستند که می تواند حمل را به تأخیر بیندازد.

با استفاده از فرآیند ذکر شده در زیر، سریعاً منبع مشکلات واقعی را ردیابی کنید.

۶۳- از ابتدا به طور مرتب بر روی دستگاه های هدف آزمایش کنید. دستگاه ها دارای ویژگی های عملکرد بسیار متفاوت هستند. از آن ها تعجب نکنید هرچه زودتر از مشکلات آگاهی داشته باشید، می توانید به طور موثرتری آن ها را برطرف کنید.

۶۴- بیاموزید که چگونه از یک پروفایلر برای ردیابی علت مشکلات عملکردی به طور موثر استفاده کنید.

  • بیاموزید که چگونه فریم های خود را تعریف کنید (با استفاده از Profiler.BeginFrame و Profiler.EndFrame) برای تحلیل دانه دانه.
  • بیاموزید که چگونه از پروفایل مخصوص پلتفرم مانند پروفایل داخلی برای iOS استفاده کنید.
  • یادگیری پروفایل برای بایگانی در پخش کننده های داخلی و نمایش داده ها در پروفایلر را بیاموزید.

۶۵- در صورت لزوم برای پروفایل دقیق تر از پروفایلر سفارشی استفاده کنید. گاهی اوقات، پروفایلر یونیتی نمی تواند تصویر واضحی از آنچه در جریان است را به شما ارائه دهد: ممکن است فریم های پروفایل آن تمام شود یا پروفایل عمیق می تواند سرعت بازی را به حدی کاهش دهد که آزمایشات معنی دار نباشند. ما برای این منظور از پروفایلر داخلی خود استفاده می کنیم، اما شما باید بتوانید گزینه های دیگری را در Asset Store پیدا کنید.

۶۶- تأثیر پیشرفت های عملکرد را اندازه گیری کنید. هنگامی که برای افزایش عملکرد تغییری ایجاد می کنید، آن را اندازه بگیرید تا مطمئن شوید این تغییر یک پیشرفت واقعی است. اگر تغییر قابل اندازه گیری نیست، آن را بی اثر کنید.

۶۷- برای عملکرد بهتر کد کمتر خوانا ننویسید. مگر اینکه:

  • مشکلی دارید، منبع را با یک پروفایلر شناسایی کردید، پس از تغییر سود را اندازه گیری کردید و سود در مقایسه با از دست دادن قابلیت نگهداری به اندازه کافی زیاد است.

یا

  • می دانید چیکار دارید میکنید.

نامگذاری استاندارد و ساختار پوشه

۶۸- از نامگذاری مستند و ساختار پوشه ای پیروی کنید. نامگذاری بدون تناقض و ساختار پوشه، یافتن چیزها و فهمیدن اینکه چه چیزهایی هستند را آسان می کند.

شما به احتمال زیاد می خواهید قرارداد نامگذاری و ساختار پوشه خود را ایجاد کنید. در اینجا یکی به عنوان مثال آورده شده است.

نامگذاری اصول کلی

۱- یک چیز را هر آنچه که هست، صدا بزنید. یک پرنده را باید پرنده نامید.

۲- اسامی را انتخاب کنید که قابل تلفظ و به خاطر سپردن باشند. اگر یک بازی مایان ساختید، سطح خود را QuetzalcoatisReturn نگذارید.

۳- مقاوم باش. وقتی نامی را انتخاب کردید، به آن پایبند باشید. چیزی را دکمه نگهدارنده در یک مکان و دکمه کانتینر را در مکان دیگر صدا نکنید.

۴- از حروف Pascal استفاده کنید، مانند این: ComplicatedVerySpecificObject. از فضاها، آندرلاین ها یا خط فاصله استفاده نکنید.

۵- از شماره نسخه یا کلمات برای نشان دادن پیشرفت آن ها استفاده نکنید (WIP، final).

۶- از اختصارات استفاده نکنید: DVamp @ W باید DarkVampire @ Walk باشد.

۷- از اصطلاحات در سند طراحی استفاده کنید: اگر سند انیمیشن die را Die می نامید، از DarkVampire @ Die استفاده کنید، نه از DarkVampire @ Death.

۸- مشخص ترین توصیفگر را در سمت چپ نگه دارید: DarkVampire، نه VampireDark. PauseButton، نه ButtonPaused. به عنوان مثال یافتن دکمه pause در بازرس آسان تر است، در صورتی که همه دکمه ها با کلمه Button شروع نشوند. [بسیاری از افراد برعکس آن را ترجیح می دهند، زیرا این امر گروه بندی را از نظر بصری آشکارتر می کند. نام ها برای گروه بندی نیستند، پوشه ها هستند. نام ها برای تشخیص اشیا از همان نوع است تا بتوان آن ها را به طور مطمئن و سریع قرار داد.]

۹- برخی از نام ها توالی تشکیل می دهند. از اعداد در این نام ها استفاده کنید، به عنوان مثال ، PathNode0 ، PathNode1. همیشه با ۰ شروع کنید، نه با ۱٫

۱۰- برای چیزهایی که توالی تشکیل نمی دهند از اعداد استفاده نکنید. به عنوان مثال ، Bird0 ، Bird1 ، Bird2 باید Flamingo ،Eagle ، Swallow باشند.

۱۱- اشیا موقت را پیشوند با دابل آندرلاین مشخص کنید. Player_Backup __.

نامگذاری جنبه های مختلف یک چیز

از آندرلاین بین نام اصلی و چیزی که “جنبه” را توصیف می کند استفاده کنید. برای مثال:

دکمه های GUI حالت EnterButton_Active ، EnterButton_Inactive را نشان می دهد

Texture ها DarkVampire_Diffuse ، DarkVampire_Normalmap

JungleSky_Top Skybox ، JungleSky_North

گروه های LOD DarkVampire_LOD0 ، DarkVampire_LOD1

از این کنوانسیون فقط برای تمیز دادن بین انواع مختلف موارد استفاده نکنید، به عنوان مثال Rock_Small ، Rock_Large بایدSmallRock ، LargeRock باشد.

ساختار

سازمان دهی صحنه ها، پوشه پروژه و پوشه اسکریپت شما باید از الگوی مشابهی پیروی کند. در اینجا چند مثال مختصر برای شروع آورده شده است.

ساختار پوشه

MyGame

Helper

Design

Scratchpad

Materials

Meshes

Actors

DarkVampire

LightVampire

Structures

Buildings

Props

Plants

Resources

Actors

Items

Prefabs

Actors

Items

Scenes

Menus

Levels

Scripts

Tests

Textures

UI

Effects

UI

MyLibray

Plugins

SomeOtherAsset1

SomeOtherAsset2

ساختار صحنه

Main

Debug

Managers

Cameras

Lights

UI

Canvas

HUD

PauseMenu

World

Ground

Props

Structures

Gameplay

Actors

Items

Dynamic Objects

ساختار پوشه اسکریپت

Debug

Gameplay

Actors

Items

Framework

Graphics

UI

۳۰ نکته ای که هر کاربر نرم افزار Unity باید بداند

profile name
میلاد حیدری

بخندید کتاب بخونید و خوب باشید تا جامعه مون به آرامش برسه. لطفا ! هر سوالی دارید در بخش نظرات مطرح کنید. ما یا سایر هموطنان عزیز پاسخ خواهیم داد. برای کمک به سایت ما و گسترش آموزش در بین هموطنان، در سایتها، وبلاگ ها و شبکه های اجتماعی لینک سایت ما را درج کنید.

مطالب پیشنهادی برای شما

محصولات مرتبط

مشاهده همه
آموزش ریزن (Reason) از صفر تا صد
آموزش ریزن (Reason) از صفر تا صد
قیمت محصول 11٪ 310,000 تومان 350,000
ادامه مطلب
آموزش تکلا استراکچر از صفر تا صد
آموزش تکلا استراکچر از صفر تا صد
قیمت محصول 147,000 تومان 159,000
ادامه مطلب

دیدگاهتان را بنویسید

1 2 3 4 5

1 نظر درباره «۶۷ ترفند و بهترین تمرین ها برای Unity (همه نسخه ها)»

  • سید عبدالحمید حسینی
    سید عبدالحمید حسینی آیا این دیدگاه مفید بود ؟

    سلام علیکم
    مطلب خیلی مفیدی بود، ممنون از انتشار، چند تا نکته به ذهنم رسید که نوشتنش خالی از لطف نیست؛
    به نظر میرسه از منبع خارجی (زبان انگلیسی) با موتور مترجم به فارسی برگردونده شده است و در بعضی موارد برخی اصطلاحات اصلا قابل فهم نیست. بعضی جاها باید عین کلمه رو داخل پرانتز قرار میگرفت بیشتر ابهام‌ها رفع می‌شد.

    با تشکر

    پاسخ
مشاهده همه نظرات
سبد خرید
سبد خرید شما خالی است
× جهت نصب روی دکمه زیر در گوشی کلیک نمائید
آی او اس
سپس در مرحله بعد برروی دکمه "Add To Home Screen" کلیک نمائید