چند هفته اخیر وقت اضافه ای که داشتم. یعنی وقتی نه روی بازی مشتری کار می کردم و نه دنبال کارایی بودم که برای سرمایه گرفتن برای بازی خودمون لازم بودش داشتم ECS یاد می گرفتم. همه مستنداتی که داشتن رو چند بار خوندم و همه ویدیو های ضبط شده رو دیدم و یه کم هم سعی کردم مثلا فلان الگوریتم رو برای خودم پیاده کنم. توی این پست می خوام راجع به ECS بگم که چیه و باید برای حل مساله باهاش چه طوری فکر کنید و چرا باید ازش استفاده بکنیم. من توی این پست سیستم سکه خوردن یه بازی platform رو براتون توضیح می دم قبلا چه طور می نوشتیم و با ECS چه طور می نویسیم و اگه کلا نمی دونید ECS چیه و چرا باید استفاده بکنیم یا تو کامنتا بگید بعدا پست بنویسم یا به منابع موجود مراجعه کنید. فرض من اینه که بیشتریا نمی فهمند مسایلشونو با ECS چه طور باید پیاده کنند.
فرض کنید یه سیستم سکه هم داریم که اگه بین کاراکترایی که می تونن اونا رو بخورن و سکه ها برخورد اتفاق افتاد به تعداد سکه اونا اضافه می کنه. تو حالت معمولی یونیتی می اومدین و یه component تعریف می کردین به اسم Coin که هر وقت مثلا OnTriggerEnter صدا زده می شد و طرف مقابل بازی کن بود براش یه متدی صدا می کردین که سکه ها شو زیاد می کرد و اگه لازم بود جونش رو هم زیاد می کرد مثلا اگه 100 تا می شدن سکه ها. تو ECS میاید یه سیستم تعریف می کنید که همه entity هایی که coin بهشون وصله رو می گیره و بعد اگه یه کاراکتری بیش از حد بهش نزدیک بود میاد متوجه می شه که باید به اون کاراکتر سکه اضافه کنه و سکه رو از بین ببره. البته می تونید این کار رو به شکل برعکس هم انجام بدین که مثلا برای همه کاراکتر ها بگرده و اگه سکه ای نزدیکشون بود از بین ببره و بهشون اضافه بکندش.
اگه دقت کنید کل بازی این شکلی می شه که شما یه سری سیستم می نویسید که اول میان همه entity هایی که یه سری component خاص رو دارند پیدا می کنند و بعدش روی همشون یه منطقی اجرا می کنند. سوال اولی که پیش میاد اینه که بین این سیستم ها چه طور اطلاعات جا به جا کنیم و سوال دوم این که اطلاعات عمومی بازی که معمولا تو singleton ها بودن رو چه طوری بهشون بدیم و سوال سوماین که چه طوری بیایم اولویت اجراشونو مشخص کنیم و از همه اینا سختتر و مهمتر این که بهترین روش نوشتن چیه
در واقع حلقه اصلی بازی بدین شکل می شه که ما میایم یه سری سیستم رو به شکل job تعریف می کنیم و بعد به نوبت متد های Schedule اونا رو صدا می کنیم و dependency هاشونو که برمی گردونن به Job های دیگه می دیم. حالا بقیه مطلب رو یونیتی هندل می کنه و به ما می گه اگه مثلا روی داده هی همزمان دوتا جاب بنویسند و ... بعد برای این که مثلا بتونیم کارمونو راحت کنیم باید بین این سیستم ها اطلاعات جا به جا کنیم که تو یونیتی با Native Array و رفقای Native دیگش انجام می شه. مثلا ما داده سکه ها و کاراکتر های نزدیک به هم رو که تو یه سیستم جمع کردیم به یه لیست اضافه می کنیم که به سیستم دیگه ای پاس داده می شه که همه اون سکه ها بعدش از بین می رن و اون سیستم میاد به همه اون کاراکتر ها امتیاز اضافه می کن ه و اگه امتیاز از 100 بیشتر شد یه جون اضافه می کنه. تفاوت این روش با سیستم های قدیمی که کد می نوشتیم دوتا چیز مهمه.
اول این که همه اطلاعاتی که با هم تو اجرای منطق شریکند تو یه آرایه کنار هم قرار می گیرند و بدون هیچ cache missی در دسترس قرار می گیرند (دقت کنیم حافظه کندترین عامل کامپیوترای امروز ما هستش) و دوما ارتباط بین این دو کاملا مشخص و روشن هست و ما دقیقا ورودی و خروجی یه سیستم رو می تونیم ببینیم و ببینیم نسبت به ورودیش چه خروجی داده و دیگه کلاس هامون خیلی آزاد وسط کارشون کلاسای دیگه رو صدا نمی زنن و دیگه اون قدرم سوال پیش نمیاد که کی باید کیو صدا کنه و فلان منطق تو کدوم سیستم باید باشه و همه چی نسبت به شکل داده ای که داریم و قراره باهاش کار کنیم اتفاق می افته.
شاید چند سوال پیش بیاد که مثلا برخورد بین سکه و آدما تو ECS چه طور تشخیص داده می شه (تو مدلی که نخوایم از APIهای قدیمی یونیتی استفاده کنیم). دو راه هستش، یکی این که جای اشیا رو توی یه hash map قرار بدیم و بعد برای تشخیص برخورد ببینیم تو کدوم خونه های همسایه هر کاراکتر سکه ای هست که فاصلش از یه مقداری باهاش کمتره و راه دوم انجام raycast توسط job مربوط یعنی RayCastCommand هستش.
یه سوال دیگه ممکنه پیش بیاد که باید سیستم رو این طور بنویسیم که روی سکه ها حلقه بزنیم و همشونو یکی یکی چک کنیم و یا روی کاراکتر ها، باید بگم که چون تعداد کاراکترا همیشه کمتره و حله سریعتر و کوچیکتر می شه بهتره روی همه کاراکترایی که می تونن سکه بخورن یه سیستم بنویسیم که چک بشه. یه راه دیگه هم این هست که سیستم برخوردی داشته باشیم که کلا لیستی از همه اشیایی که با هم برخورد دارند می سازه و بعدش سیستم های دیگه می تونن این لیست رو پردازش کنند و در آخر لیست می تونه پاک بشه. این که کدوم راه بهتره بسته به اینه که شما آیا بتونید CPU اضافه مصرف کنید و یا حافظه و بسته به بقیه سیستمای بازیتون داره، معمولا برای یه platformer به یه سیستم برخورد عمومی نیاز خواهید داشت.
یه مساله دیگه که خوبه بدونید اینه که یونیتی همه سیستماشو یکی یکی به شکل jobified/ECS در میاره و از اون جا قابل استفاده می شه. مهم اینه که شما یاد بگیرید برای نوشتن کد با ECS باید به جای دیدن این که فلان object/component چه رفتارایی داره این رو ببینید که من به چه داده هایی نیاز دارم که این منطق رو اجرا کنم و چه عملیاتی باید روی این داده ها انجام بدم و برای ارتباط بین این عملیاتا کجا ها به ساختمان داده میانی اضافه نیاز هستش. مثلا برای ارتباط بین سیستم برخورد و سیستم از بین رفتن سکه و امتیاز دادن به داده میانی لیست/صف نیازه ولی برای این که بعد از زیاد شدن سرعت به دلیل خوردن Power up سیستم حرکت کاراکتر شما رو سریعتر حرکت بده نیازی به یه لیست نیست که همه کاراکترایی که سرعتشون اضافه شده باشه داشته باشیم چون سیستمی که داره اثر پاور آپ روی کاراکتر رو چک می کنه می تونه با علم به این که این power up از نوع تغییر سرعت هست بگه من اون entity هایی رو می خوام که کامپوننت سرعت دارند و کامپوننت آدم و بعد اونایی که تو لیست برخورد با پاور آپ سرعت هستند رو اون پاور آپ مربوط رو بگه براشون پاک بشه و بعد همون کامپوننت سرعت رو درجا سرعتشو مثلا دو برابر کنه و اگه تایمی هم داره اون رو هم ست بکنه که مثلا ثانیه x این سرعت بالا رفته. می تونیم سیستم دیگه ای داشته باشیم که همه کامپوننت های سرعت رو چک می کنه و اگه از xشون 5 ثانیه گذشته باشه دوباره به سرعت عادی برشون گردونه.
یه مطلب دیگه مساله سختن/از بین بردن entity هست و اضافه/کم کردن component که همیشه باید روی thread اصلی انجام شه و معمولا وسط سیستم هایی که Multi-threaded هستند که بهشون JobComponentSystem گفته می شه قابل اجرا نیستند. به جاش یه بافری هست که توی اون سیستم پر می شه و بعد در دیر ترین زمان ممکن اجرا می شه.
مسایل دیگه ای مثل barrier ها و این که مثلا اضافه /کم کردن component به entity از نظر performance چه باری داره و این که کلا entity ها و در واقع آرایه های component ها چه طور روی حافظه چیده می شند یا با کدوم API ها باید این کارارو بکنیم رو توضیح ندادم چون که اولا خسته هستم و دوما به نظرم مساله مهم اینه که یاد بگیرید چه طور فکر کنید که کد ECS بنویسید و اون API ها رو به سادگی یاد می گیرید. خودم هم نوشتن sampleم یحتمل می ره واسه بعد GDC که نسخه نسبتا stableتری از اینا بیاد بیرون. الآن هم کاملا ممکنه و من کد های یونیتی رو به چیزایی که دلم خواست تغییر دادم ولی بخشای کوچیک ی گنگ هستند و تغییر کردن و چون فرصت کمه فعلا سراغ ساخت سمپل کامل نرفتم.
اگه سوالی داشتید یا پست معرفی ابتدایی خود ECS خواستید تو کامنتا بگید لطفا ، اگه هم احساس کردین جایی اشتباه می کنم بگید چون خودمم هم دارم تازه یاد می گیرم.