Гульня на Rust за 24 гадзіны: асабісты досвед распрацоўкі
У гэтым артыкуле я распавяду пра асабісты досвед распрацоўкі невялікай гульні на Rust. На стварэнне працоўнай версіі спатрэбілася каля 24 гадзін (пераважна я працавала па вечарах ці на выходных). Гульня яшчэ далёкая ад завяршэння, але я думаю, што досвед будзе карысным. Я раскажу, чаму навучылася, і пра некаторыя назіранні, зробленыя пры пабудове гульні з нуля.
Нагадваем:для ўсіх чытачоў "Хабра" - зніжка 10 000 рублёў пры запісе на любы курс Skillbox па промакодзе "Хабр".
Чаму Rust?
Я выбрала гэтую мову, паколькі чула пра яе шмат добрага і бачу, што яна становіцца ўсё больш папулярнай у сферы распрацоўкі гульняў. Да напісання гульні ў мяне быў невялікі досвед распрацоўкі простых прыкладанняў на Rust. Гэтага было якраз дастаткова, каб адчуць пэўную свабоду падчас напісання гульні.
Чаму менавіта гульня і што за гульня?
Стварэнне гульняў - гэта весела! Я б хацела, каб прычын было больш, але для "хатніх" праектаў я выбіраю тэмы, якія не занадта цесна звязаныя з маёй звычайнай працай. Што за гульня? Мне хацелася зрабіць нешта накшталт тэніснага сімулятара, дзе спалучаюцца Cities Skylines, Zoo Tycoon, Prison Architect і ўласна тэніс. Увогуле атрымалася гульня аб акадэміі тэніса, куды людзі прыходзяць для гульні.
Тэхнічная падрыхтоўка
Мне хацелася выкарыстоўваць Rust, але я не ведала дакладна, наколькі "з нуля" спатрэбіцца пачаць працу. Я не хацела пісаць піксельныя шэйдары і выкарыстоўваць drag-n-drop, таму шукала самыя гнуткія рашэнні.
Знайшла карысныя рэсурсы, якімі дзялюся з вамі:
Are we game yet - спіс патрэбных для распрацоўкі гульняў элементаў Rust;
Я вывучыла некалькі гульнявых рухавічкоў Rust, абраўшы ў канчатковым выніку Piston і ggez. З імі я сутыкалася пры працы над папярэднім праектам. У выніку выбрала ggez, паколькі ён здаўся больш прыдатным для рэалізацыі невялікай 2D-гульні. Модульная структура Piston залішне складаная для пачаткоўца распрацоўніка (ці таго, хто ўпершыню працуе з 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 неабходны таму, што ў ім - усе радкі кода. Я патраціла крыху часу, каб падзяліць файлы і аптымізаваць структуру дырэкторый. Вось як усё стала выглядаць пасля гэтага: resources -> гэта where all the assets are (images)
SRC
- entities
- game_object.rs
- circle.rs
- main.rs -> main loop
Людзі, паверхі і выявы
Наступны этап - стварэнне гульнявога аб'екта Person і загрузка малюнкаў. Усё павінна будавацца на аснове плітак памерам 32*32.
тэнісныя корты
Вывучыўшы, як выглядаюць тэнісныя корты, я вырашыла зрабіць іх з плітак 4*2. Першапачаткова можна было зрабіць выяву такога памеру або скласці разам 8 асобных плітак. Але потым я зразумела, што неабходны толькі дзве ўнікальныя пліткі, і вось чаму.
Усяго ў нас дзве такія пліткі: 1 і 2.
Кожная секцыя корта складаецца з пліткі 1 ці пліткі 2. Яны могуць размяшчацца як звычайна ці быць перавернутымі на 180 градусаў.
Асноўны рэжым будаўніцтва (зборкі)
Пасля таго як атрымалася дамагчыся рэндэрынгу пляцовак, людзей і карт, я зразумела, што неабходны яшчэ і базавы рэжым зборкі. Яго рэалізавала так: калі націснутая кнопка - аб'ект абраны, а клік размяшчае яго на патрэбным месцы. Так, кнопка 1 дае магчымасць абраць корт, а кнопка 2 дазваляе абраць гульца.
Але ж трэба яшчэ запомніць, што ў нас азначае 1 і 2, таму я дадала вайрфрэйм для таго, каб было зразумела, які аб'ект абраны. Вось як гэта выглядае.
Пытанні па архітэктуры і рэфактарынгу
Цяпер у мяне ёсць некалькі гульнявых аб'ектаў: людзі, корты і паверхі. Але для таго, каб працавалі вайрфрэймы, трэба кожнай сутнасці аб'екта паведаміць, ці знаходзяцца самі аб'екты ў рэжыме дэманстрацыі, ці проста намаляваная рамка. Гэта ня вельмі зручна.
Мне падалося, што трэба пераасэнсаваць архітэктуру так, каб выявіліся некаторыя абмежаванні:
наяўнасць сутнасці, якая адлюстроўвае і абнаўляе сябе саму, - гэта праблема, паколькі гэтая сутнасць не зможа "даведацца", што яна павінна рэндэрыць - малюнак і вайрфрэйм;
адсутнасць прылады абмену ўласцівасцямі і паводзінамі паміж асобна ўзятымі сутнасцямі (прыклад - уласцівасць is_build_mode ці ж адмалёўка паводзін). Можна было б выкарыстоўваць атрыманне ў спадчыну, хоць у Rust няма нармальнага спосабу яго рэалізацыі. Што мне сапраўды было патрэбна - гэта кампаноўка;
прылада для ўзаемадзеяння сутнасцяў паміж сабой быў патрэбен, каб прызначаць людзей у корты;
самі сутнасці ўяўлялі сабою сумесь дадзеных і логікі, што вельмі хутка выходзіла з-пад кантролю.
Я правяла дадатковае вывучэнне і выявіла архітэктуру ECS – Entity Component System, Якая звычайна выкарыстоўваецца ў гульнях. Вось перавагі ECS:
дадзеныя аддзеленыя ад логікі;
кампаноўка замест атрымання ў спадчыну;
архітэктура, арыентаваная на дадзеныя.
Для ECS характэрны тры базавыя канцэпты:
сутнасці - тып аб'екта, на які спасылаецца ідэнтыфікатар (гэта можа быць гулец, мячык ці нешта яшчэ);
кампаненты - з іх складаюцца сутнасці. Прыклад - кампанент рэндэрынгу, размяшчэння і іншыя. Гэта сховішчы дадзеных;
сістэмы - яны выкарыстоўваюць як аб'екты, так і кампаненты, плюс змяшчаюць паводзіны і логіку, якія грунтуюцца на гэтых дадзеных. Прыклад - сістэма рэндэрынгу, якая перабірае ўсе сутнасці з кампанентамі для рэндэрынгу і займаецца адмалёўкай.
Пасля вывучэння стала зразумела, што ECS вырашае такія праблемы:
ужыванне кампаноўкі замест атрымання ў спадчыну для сістэмнай арганізацыі сутнасцяў;
збавенне ад мешаніны кода за кошт сістэм кіравання;
выкарыстанне метадаў накшталт is_build_mode, каб захоўваць логіку вайрфрэйма ў адным і тым жа месцы - у сістэме рэндэрынгу.
Вось што атрымалася пасля ўкаранення ECS.
resources -> гэта where all the assets are (images)
SRC
- components
- position.rs
- person.rs
- tennis_court.rs
- floor.rs
- wireframe.rs
- mouse_tracked.rs
- resources
- mouse.rs
- systems
- rendering.rs
- constants.rs
- utils.rs
- world_factory.rs -> world factory functions
- main.rs -> main loop
Прызначаем людзей на корты
ECS зрабіла жыццё прасцей. Цяпер у мяне быў сістэмны шлях дадання дадзеных да сутнасцяў і даданне логікі, заснаванай на гэтых дадзеных. А гэта, у сваю чаргу, дазволіла арганізаваць размеркаванне людзей па кортах.
Што я зрабіла:
дадала дадзеныя аб прызначаных кортах у Person;
дадала дадзеныя аб размеркаваных людзях у TennisCourt;
дадала CourtChoosingSystem, якая дазваляе аналізаваць людзей і пляцоўкі, выяўляць даступныя корты і размяркоўваць гульцоў на іх;
дадала сістэму PersonMovementSystem, якая шукае людзей, прызначаных на корты, і калі іх тамака няма, то адпраўляе людзей куды трэба.
Падводзім вынікі
Мне вельмі спадабалася працаваць над гэтай простай гульнёй. Больш за тое, я задаволеная, што выкарыстоўвала для яе напісання Rust, паколькі:
Rust дае вам тое, што неабходна;
у яго выдатная дакументацыя, Rust вельмі элегантны;
сталасць - гэта крута;
не прыходзіцца звяртацца да кланавання, капіяванні ці іншым падобным дзеянням, што я часта рабіла ў З++;
Options вельмі зручныя для працы, яны таксама цудоўна апрацоўваюць памылкі;
калі праект удалося скампіляваць, то ў 99% ён працуе, прычым менавіта так, як павінен. Паведамлення пра памылкі кампілятара, мне здаецца, лепшыя з тых, што я бачыла.
Распрацоўка гульняў на Rust зараз толькі пачынаецца. Але ўжо ёсць стабільнае і даволі вялікае кам'юніці, якое працуе над тым, каб адкрыць Rust для ўсіх. Таму я гляджу на будучыню мовы з аптымізмам, з нецярпеннем чакаючы вынікаў нашай супольнай працы.