در این مقاله در مورد تجربه شخصی خود از توسعه یک بازی کوچک در Rust صحبت خواهم کرد. حدود 24 ساعت طول کشید تا یک نسخه کارآمد ایجاد شود (من بیشتر عصرها یا آخر هفته ها کار می کردم). بازی هنوز به پایان نرسیده است، اما فکر میکنم این تجربه برای شما مفید خواهد بود. آنچه را که یاد گرفتم و مشاهداتی که در حین ساخت بازی از ابتدا انجام دادم را به اشتراک خواهم گذاشت.
یادآوری می کنیم:برای همه خوانندگان "Habr" - تخفیف 10 روبل هنگام ثبت نام در هر دوره Skillbox با استفاده از کد تبلیغاتی "Habr".
چرا زنگ؟
من این زبان را انتخاب کردم زیرا چیزهای خوب زیادی در مورد آن شنیده ام و می بینم که در ساخت بازی محبوب تر می شود. قبل از نوشتن بازی، تجربه کمی در توسعه برنامه های کاربردی ساده در Rust داشتم. همین کافی بود تا در حین نوشتن بازی حس آزادی به من بدهد.
چرا بازی و چه نوع بازی؟
ساخت بازی سرگرم کننده است! ای کاش دلایل بیشتری وجود داشت، اما برای پروژههای «خانه» موضوعاتی را انتخاب میکنم که خیلی مرتبط با کار معمولی من نیستند. این چه بازیه من می خواستم چیزی شبیه شبیه ساز تنیس بسازم که Cities Skylines، Zoo Tycoon، Prison Architect و خود تنیس را ترکیب کند. به طور کلی، معلوم شد که این یک بازی در مورد یک آکادمی تنیس است که در آن مردم برای بازی می آیند.
آموزش فنی
من میخواستم از Rust استفاده کنم، اما نمیدانستم دقیقاً برای شروع آن چقدر زمینه لازم است. من نمی خواستم سایه بان پیکسل بنویسم و از کشیدن-n-drop استفاده کنم، بنابراین به دنبال انعطاف پذیرترین راه حل ها بودم.
منابع مفیدی پیدا کردم که با شما به اشتراک می گذارم:
من چندین موتور بازی Rust را بررسی کردم و در نهایت Piston و ggez را انتخاب کردم. در حین کار روی پروژه قبلی با آنها برخورد کردم. در نهایت ggez را انتخاب کردم چون برای اجرای یک بازی کوچک دو بعدی مناسب تر به نظر می رسید. ساختار ماژولار پیستون برای یک توسعه دهنده تازه کار (یا شخصی که برای اولین بار با Rust کار می کند) بسیار پیچیده است.
ساختار بازی
مدتی را صرف فکر کردن به معماری پروژه کردم. اولین قدم ساختن «زمین»، مردم و زمین تنیس است. مردم باید در دادگاه ها حرکت کنند و منتظر بمانند. بازیکنان باید مهارت هایی داشته باشند که به مرور زمان بهبود یابد. به علاوه، باید ویرایشگری وجود داشته باشد که به شما امکان می دهد افراد و دادگاه های جدید را اضافه کنید، اما این دیگر رایگان نیست.
با فکر کردن به همه چیز، دست به کار شدم.
ساخت بازی
آغاز: دایره ها و انتزاعات
من از ggez مثال زدم و یک دایره روی صفحه گرفتم. شگفت انگیز! حالا چند انتزاع فکر کردم خوب است که از ایده یک شی بازی انتزاع بگیرم. هر شی باید همانطور که در اینجا ذکر شده است رندر و به روز شود:
// the game object trait
trait GameObject {
fn update(&mut self, _ctx: &mut Context) -> GameResult<()>;
fn draw(&mut self, ctx: &mut Context) -> GameResult<()>;
}
// a specific game object - Circle
struct Circle {
position: Point2,
}
impl Circle {
fn new(position: Point2) -> Circle {
Circle { position }
}
}
impl GameObject for Circle {
fn update(&mut self, _ctx: &mut Context) -> GameResult<()> {
Ok(())
}
fn draw(&mut self, ctx: &mut Context) -> GameResult<()> {
let circle =
graphics::Mesh::new_circle(ctx, graphics::DrawMode::Fill, self.position, 100.0, 2.0)?;
graphics::draw(ctx, &circle, na::Point2::new(0.0, 0.0), 0.0)?;
Ok(())
}
}
این قطعه کد لیست خوبی از اشیاء را به من داد که میتوانم آنها را در یک حلقه به همان اندازه خوب بهروزرسانی و ارائه کنم.
mpl event::EventHandler for MainState {
fn update(&mut self, context: &mut Context) -> GameResult<()> {
// Update all objects
for object in self.objects.iter_mut() {
object.update(context)?;
}
Ok(())
}
fn draw(&mut self, context: &mut Context) -> GameResult<()> {
graphics::clear(context);
// Draw all objects
for object in self.objects.iter_mut() {
object.draw(context)?;
}
graphics::present(context);
Ok(())
}
}
main.rs ضروری است زیرا حاوی تمام خطوط کد است. من زمان کمی را صرف جداسازی فایل ها و بهینه سازی ساختار دایرکتوری کردم. بعد از آن اینگونه به نظر می رسید: منابع -> این جایی است که همه دارایی ها هستند (تصاویر)
"
- موجودیت ها
- game_object.rs
- حلقه.rs
— main.rs -> حلقه اصلی
افراد، کف و تصاویر
مرحله بعدی ایجاد یک شی بازی Person و بارگذاری تصاویر است. همه چیز باید بر اساس کاشی های 32*32 ساخته شود.
زمین تنیس
پس از مطالعه ظاهر زمین های تنیس، تصمیم گرفتم آنها را از کاشی های 4*2 بسازم. در ابتدا امکان ساخت تصویری به این اندازه یا کنار هم قرار دادن 8 کاشی جداگانه وجود داشت. اما بعد متوجه شدم که فقط به دو کاشی منحصر به فرد نیاز است و دلیل آن این است.
در مجموع ما دو کاشی داریم: 1 و 2.
هر بخش از زمین از کاشی 1 یا کاشی 2 تشکیل شده است. آنها را می توان به صورت عادی و یا 180 درجه برگرداند.
حالت ساخت و ساز اولیه (مونتاژ).
بعد از اینکه توانستم به رندر کردن سایت ها، افراد و نقشه ها برسم، متوجه شدم که یک حالت اسمبلی اولیه نیز لازم است. من آن را به این صورت اجرا کردم: وقتی دکمه فشار داده می شود، شی انتخاب می شود و کلیک آن را در محل مورد نظر قرار می دهد. بنابراین، دکمه 1 به شما امکان می دهد یک زمین را انتخاب کنید، و دکمه 2 به شما امکان می دهد یک بازیکن را انتخاب کنید.
اما ما هنوز باید به یاد داشته باشیم که 1 و 2 به چه معنا هستند، بنابراین من یک Wireframe اضافه کردم تا مشخص کنم کدام شی انتخاب شده است. این چیزی است که به نظر می رسد.
سوالات معماری و بازسازی
اکنون چندین شیء بازی دارم: مردم، زمین و طبقه. اما برای اینکه وایرفریم ها کار کنند، به هر موجودیت شی باید گفته شود که آیا خود اشیا در حالت نمایش هستند یا اینکه یک فریم به سادگی ترسیم شده است. این خیلی راحت نیست.
به نظرم می رسید که معماری باید به گونه ای تجدید نظر شود که برخی از محدودیت ها را آشکار کند:
وجود موجودی که خود را رندر و بهروزرسانی میکند یک مشکل است زیرا آن موجودیت نمیتواند آنچه را که قرار است رندر کند - یک تصویر و یک قاب سیمی - «بداند».
فقدان ابزاری برای تبادل خصوصیات و رفتار بین موجودیتهای منفرد (به عنوان مثال، ویژگی is_build_mode یا رندر رفتار). استفاده از وراثت ممکن است، اگرچه راه مناسبی برای پیاده سازی آن در Rust وجود ندارد. چیزی که من واقعاً به آن نیاز داشتم طرح بندی بود.
ابزاری برای تعامل بین نهادها برای انتساب افراد به دادگاه ها مورد نیاز بود.
خود موجودیت ها ترکیبی از داده ها و منطق بودند که به سرعت از کنترل خارج شدند.
من تحقیقات بیشتری انجام دادم و معماری را کشف کردم ECS - Entity Component System، که معمولا در بازی ها استفاده می شود. در اینجا مزایای ECS وجود دارد:
داده ها از منطق جدا می شوند.
ترکیب به جای ارث.
معماری داده محور
ECS با سه مفهوم اساسی مشخص می شود:
موجودیت ها - نوع شیئی که شناسه به آن اشاره می کند (می تواند بازیکن، توپ یا چیز دیگری باشد).
اجزاء - موجودیت ها از آنها تشکیل شده اند. مثال - رندر مؤلفه، مکانها و موارد دیگر. اینها انبارهای داده هستند.
سیستمها - آنها هم از اشیاء و هم از اجزاء استفاده میکنند، به علاوه رفتار و منطقی را که بر اساس این دادهها است، دارند. یک مثال یک سیستم رندر است که در تمام موجودیتها با اجزای رندر تکرار میشود و رندر را انجام میدهد.
پس از مطالعه آن مشخص شد که ECS مشکلات زیر را حل می کند:
استفاده از چیدمان به جای وراثت برای سازماندهی سیستمی موجودیت ها.
خلاص شدن از درهم آمیختگی کد از طریق سیستم های کنترل.
با استفاده از روش هایی مانند is_build_mode برای نگه داشتن منطق wireframe در همان مکان - در سیستم رندرینگ.
این چیزی است که پس از اجرای ECS اتفاق افتاد.
منابع -> این جایی است که همه دارایی ها هستند (تصاویر)
"
- اجزاء
- position.rs
- person.rs
- tennis_court.rs
- floor.rs
- wireframe.rs
- mouse_tracked.rs
- منابع
- mouse.rs
- سیستم های
- rendering.rs
- ثابت ها.rs
- utils.rs
— world_factory.rs -> توابع کارخانه جهان
— main.rs -> حلقه اصلی
ما افراد را به دادگاه معرفی می کنیم
ECS زندگی را آسان تر کرده است. حالا من یک روش سیستماتیک برای اضافه کردن داده ها به موجودیت ها و اضافه کردن منطق بر اساس آن داده ها داشتم. و این به نوبه خود امکان سازماندهی توزیع افراد در بین دادگاه ها را فراهم کرد.
من چه کار کرده ام:
دادههای مربوط به دادگاههای تعیینشده را به شخص اضافه کرد.
داده های مربوط به افراد توزیع شده را به زمین تنیس اضافه کرد.
CourtChoosingSystem اضافه شده است که به شما امکان می دهد افراد و دادگاه ها را تجزیه و تحلیل کنید، زمین های موجود را شناسایی کنید و بازیکنان را بین آنها توزیع کنید.
یک PersonMovementSystem را اضافه کرد که به دنبال افرادی است که به دادگاه اختصاص داده شده اند، و اگر آنها آنجا نباشند، سپس افراد را به جایی که باید باشند می فرستد.
جمعبندی
من واقعا از کار کردن روی این بازی ساده لذت بردم. علاوه بر این، خوشحالم که از Rust برای نوشتن آن استفاده کردم، زیرا:
زنگ آنچه را که نیاز دارید به شما می دهد.
مستندات عالی دارد، Rust بسیار ظریف است.
قوام سرد است.
شما مجبور نیستید به شبیه سازی، کپی کردن یا سایر اقدامات مشابه متوسل شوید، که من اغلب در C++ انجام می دادم.
استفاده از گزینه ها بسیار آسان است و خطاها را به خوبی مدیریت می کنند.
اگر پروژه می توانست کامپایل شود، در 99٪ مواقع کار می کند و دقیقاً همانطور که باید. من فکر می کنم پیغام های خطای کامپایلر بهترین هستند که دیده ام.
توسعه بازی در Rust تازه شروع شده است. اما در حال حاضر یک جامعه پایدار و نسبتاً بزرگ وجود دارد که تلاش می کند Rust را به روی همه باز کند. بنابراین، من با خوش بینی به آینده زبان نگاه می کنم و مشتاقانه منتظر نتایج کار مشترک خود هستم.