
В тази статия ще споделя личен опит за разработката на малка игра на Rust. За създаването на работна версия бяха необходими около 24 часа (предимно работих вечер или през уикендите). Играта все още е далеч от завършване, но смятам, че опитът ще бъде полезен. Ще разкажа какво научих и за някои наблюдения, направени при изграждането на игра от нулата.
Skillbox препоръчва: Двугодишен практически курс .
Напомняме: за всички читатели на "Хабра" — отстъпка от 10 000 рубли при записване на всякакъв курс на Skillbox с промокод "Хабр".
Защо Rust?
Избрах този език, защото чух много добри неща за него и виждам, че става все по-популярен в сферата на разработката на игри. Преди да напиша играта, имах малък опит в разработката на прости приложения на Rust. Това беше точно достатъчно, за да почувствам определена свобода по време на писането на играта.
Защо игра и каква игра?
Създаването на игри е забавно! Бих искала причини да има повече, но за „домашни“ проекти избирам теми, които не са твърде свързани с обичайната ми работа. Каква игра? Исках да направя нещо подобно на тенисен симулатор, където се комбинират Cities Skylines, Zoo Tycoon, Prison Architect и самият тенис. В крайна сметка, получи се игра за академия по тенис, където хората идват да играят.
Техническа подготовка
Исках да използвам Rust, но не знаех точно от колко „нулеви“ стъпки ще трябва да започна работа. Не исках да пиша пикселни шейдъри и да използвам drag-n-drop, затова търсех най-гъвкавите решения.
Намерих полезни ресурси, които споделям с вас:
- — списък с необходимите за разработка на игри елементи на 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 за MainState {
fn update(&mut self, context: &mut Context) -> GameResult {
// Актуализиране на всички обекти
for object in self.objects.iter_mut() {
object.update(context)?;
}
Ok(())
}
fn draw(&mut self, context: &mut Context) -> GameResult {
graphics::clear(context);
// Рендериране на всички обекти
for object in self.objects.iter_mut() {
object.draw(context)?;
}
graphics::present(context);
Ok(())
}
} main.rs е необходим, защото в него се съдържат всички редове код. Отне ми малко време да разделя файловете и оптимизирам структурата на директориите. Ето как всичко изглежда след това:
resources -> тук са всички активи (изображения)
src
— entities
— game_object.rs
— circle.rs
— main.rs -> основен цикъл
Хора, кортове и изображения
Следващият етап е създаването на игровия обект Person и зареждането на изображения. Всичко трябва да се изгражда на базата на плочки с размер 32*32.

Тенис кортове
След като проучих как изглеждат тенис кортовете, реших да ги направя от плочки 4*2. Първоначално можех да направя изображение с този размер или да събера 8 отделни плочки. Но после разбрах, че са нужни само две уникални плочки и ето защо.
Имаме общо две такива плочки: 1 и 2.
Всяка секция на корта се състои от плочка 1 или плочка 2. Те могат да бъдат разположени нормално или да бъдат обърнати на 180 градуса.

Основен режим на строителство (сглобяване)
След като успях да постигна рендериране на площадките, хората и картите, разбрах, че ми трябва и основен режим на сглобяване. Реализирах го така: когато е натиснат бутон — обектът е избран, а кликът го поставя на желаното място. Така бутон 1 дава възможност да се избере корт, а бутон 2 позволява да се избере играч.
Но необходимо е също да запомним какво означават 1 и 2, затова добавих вайрфрейм, за да бъде ясно кой обект е избран. Ето как изглежда това.

Въпроси относно архитектурата и рефакторирането
Сега имам няколко игрови обекта: хора, тенис кортове и етажи. Но за да работят вайрфреймовете, трябва на всяка същност на обекта да се съобщи дали самите обекти са в демонстрационен режим или просто е нарисувана рамка. Това не е много удобно.
Смятам, че трябва да преосмисля архитектурата, така че да се проявят някои ограничения:
- присъствието на същност, която се показва и обновява сама, е проблем, тъй като тази същност няма да може да "узнае", какво трябва да рендерира — изображение и вайрфрейм;
- липса на инструмент за обмен на свойства и поведение между отделни същности (пример — свойството is_build_mode или рендериране на поведението). Можеше да се използва наследяване, въпреки че в Rust няма нормален начин за реализиране на това. Наистина, това, от което се нуждаех, беше компоновка;
- инструмент за взаимодействие между съществата беше необходим, за да назначавам хора на кортовете;
- самите същности представляваха смесица от данни и логика, което много бързо излизаше извън контрол.
Направих допълнително проучване и открих архитектура , която обикновено се използва в игрите. Ето предимствата на ECS:
- данните са отделени от логиката;
- компоноване вместо наследяване;
- архитектура, ориентирана към данни.
За ECS са характерни три основни концепции:
- същности — тип обект, на който се отнася идентификатор (това може да е играч, топка или нещо друго);
- компоненти — от тях се състоят съществата. Пример — компонент за рендериране, местоположение и други. Това са хранилища на данни;
- системи — те използват както обекти, така и компоненти, плюс съдържат поведение и логика, които се основават на тези данни. Пример — система за рендериране, която обхожда всички същности с компоненти за рендериране и се занимава с рендерирането.
След проучването стана ясно, че ECS решава тези проблеми:
- прилагане на компоновка вместо наследяване за системна организация на съществата;
- освобождаване от объркването на кода чрез системи за управление;
- използване на методи като is_build_mode, за да се съхранява логиката на вайрфрейма на едно и също място — в системата за рендериране.
Ето какво се получи след внедряването на ECS.
resources -> тук са всички активи (изображения)
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 -> функции фабрики мира
— main.rs -> основен цикъл
Назначаваме хора на кортовете
ECS улесни живота. Сега имах системен начин за добавяне на данни към съществата и добавяне на логика, базирана на тези данни. Това, от своя страна, позволи да организираме разпределението на хората по кортовете.
Какво направих:
- добавих данни за назначените кортове в Person;
- добавих данни за разпределените хора в TennisCourt;
- добавих CourtChoosingSystem, която позволява да се анализират хора и площадки, да се откриват налични кортове и да се разпределят играчите;
- добавих система PersonMovementSystem, която търси хора, назначени на кортовете, и ако ги няма там, изпраща хората където е необходимо.

Обобщаваме
Много ми хареса да работя по тази проста игра. Освен това съм доволна, че използвах Rust за нейното написване, защото:
- Rust ви дава точно това, от което имате нужда;
- има чудесна документация, Rust е доста елегантен;
- постоянството е страхотно;
- не е необходимо да прибягвам до клониране, копиране или подобни действия, което често правех в C++;
- Options са много удобни за работа, те също така прекрасно обработват грешките;
- ако проектът успее да се компилира, то в 99% от случаите той работи точно така, както трябва. Съобщенията за грешки на компилатора, според мен, са най-добрите, които съм виждала.
Разработката на игри на Rust сега едва започва. Но вече има стабилна и доста голяма общност, която работи за това да отвори Rust за всички. Затова гледам на бъдещето на езика с оптимизъм и с нетърпение очаквам резултатите от нашата обща работа.
Skillbox препоръчва:
- Онлайн курс .
- Практически курс .
- Практически годишен курс .
Източник: habr.com
