
W tym artykule opowiem o moim osobistym doświadczeniu w tworzeniu małej gry w Rust. Stworzenie działającej wersji zajęło mi około 24 godzin (głównie pracowałam wieczorami lub w weekendy). Gra jest jeszcze daleka od ukończenia, ale myślę, że to doświadczenie będzie przydatne. Opowiem, czego się nauczyłam, oraz o kilku spostrzeżeniach, które miałam podczas budowania gry od podstaw.
Skillbox poleca: Dwuletni praktyczny kurs .
Przypominamy: dla wszystkich czytelników „Habra” — zniżka 10 000 rubli przy zapisie na dowolny kurs Skillbox z kodem promocyjnym „Habra”.
Dlaczego Rust?
Wybrałam ten język, ponieważ słyszałam o nim wiele dobrego i widzę, że staje się coraz bardziej popularny w dziedzinie tworzenia gier. Przed napisaniem gry miałam niewielkie doświadczenie w tworzeniu prostych aplikacji w Rust. Tego było wystarczająco, by poczuć pewną swobodę w trakcie pisania gry.
Dlaczego akurat gra i co to za gra?
Tworzenie gier to zabawa! Chciałabym, aby było więcej powodów, ale do „domowych” projektów wybieram tematy, które nie są zbyt ściśle związane z moją codzienną pracą. Co to za gra? Chciałam stworzyć coś w stylu symulatora tenisa, gdzie łączą się Cities Skylines, Zoo Tycoon, Prison Architect i sam tenis. W skrócie, powstała gra o akademii tenisa, gdzie ludzie przychodzą, aby grać.
Przygotowanie techniczne
Chciałam użyć Rust, ale nie wiedziałam dokładnie, od czego należy zacząć pracę „od zera”. Nie chciałam pisać pikselowych shaderów ani korzystać z drag-and-drop, dlatego szukałam najbardziej elastycznych rozwiązań.
Znalazłam przydatne zasoby, którymi dzielę się z wami:
- — lista potrzebnych elementów Rust do tworzenia gier;
Zbadałam kilka silników gier w Rust, ostatecznie wybierając Piston i ggez. Spotkałam się z nimi przy pracy nad wcześniejszym projektem. Ostatecznie wybrałam ggez, ponieważ wydawał się bardziej odpowiedni do realizacji małej gry 2D. Modułowa struktura Piston jest zbyt skomplikowana dla początkującego dewelopera (lub tego, kto pierwszy raz pracuje z Rust).
Struktura gry
Poświęciłam trochę czasu na myślenie o architekturze projektu. Pierwszym krokiem jest stworzenie „ziemi”, ludzi i kortów tenisowych. Ludzie powinni poruszać się po kortach i czekać. Gracze powinni mieć umiejętności, które doskonalą się z upływem czasu. Oprócz tego powinien być edytor, który pozwala na dodawanie nowych ludzi i kortów, ale to już nie jest darmowe.
Rozmyślając o wszystkim, przystąpiłam do pracy.
Tworzenie gry
Początek: okręgi i abstrakcje
Wzięłam przykład z ggez i uzyskałam okrąg na ekranie. Niesamowite! Teraz trochę abstrakcji. Wydaje mi się, że dobrze byłoby abstrahować od idei obiektu gry. Każdy obiekt powinien być renderowany i aktualizowany, jak pokazano tutaj:
// 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(())
}
}Ten fragment kodu pozwolił mi uzyskać świetną listę obiektów, które mogę aktualizować i renderować w równie świetnej pętli.
mpl event::EventHandler for MainState {
fn update(&mut self, context: &mut Context) -> GameResult {
// Aktualizuj wszystkie obiekty
for object in self.objects.iter_mut() {
object.update(context)?;
}
Ok(())
}
fn draw(&mut self, context: &mut Context) -> GameResult {
graphics::clear(context);
// Rysuj wszystkie obiekty
for object in self.objects.iter_mut() {
object.draw(context)?;
}
graphics::present(context);
Ok(())
}
} main.rs jest potrzebny, ponieważ zawiera wszystkie linie kodu. Poświęciłam trochę czasu na podział plików i optymalizację struktury katalogów. Tak to wszystko wygląda po tym:
resources -> to tutaj znajdują się wszystkie zasoby (obrazy)
src
— entities
— game_object.rs
— circle.rs
— main.rs -> główna pętla
Ludzie, boiska i obrazy
Następny etap to stworzenie obiektu gry Person i załadowanie obrazów. Wszystko musi być zbudowane na podstawie kafelków 32*32.

Boiska tenisowe
Badając, jak wyglądają boiska tenisowe, postanowiłam zrobić je z kafelków 4*2. Początkowo można było utworzyć obraz tego rozmiaru lub połączyć 8 oddzielnych kafelków. Ale potem zrozumiałam, że potrzebne są tylko dwie unikalne kafelki i oto dlaczego.
Mamy tylko dwie takie kafelki: 1 i 2.
Każda sekcja boiska składa się z kafelki 1 lub kafelki 2. Mogą być umieszczone w zwykły sposób lub obrócone o 180 stopni.

Główny tryb budowy (kompletacji)
Po osiągnięciu renderowania boisk, ludzi i map, zrozumiałam, że także potrzebny jest podstawowy tryb budowy. Zrealizowałam go tak: kiedy przycisk jest wciśnięty — obiekt jest wybierany, a kliknięcie umieszcza go w odpowiednim miejscu. Tak, przycisk 1 pozwala wybrać boisko, a przycisk 2 pozwala wybrać gracza.
Ale musimy też zapamiętać, co oznaczają 1 i 2, dlatego dodałam wireframe, aby było jasne, który obiekt jest wybrany. Tak to wygląda.

Pytania dotyczące architektury i refaktoryzacji
Teraz mam kilka obiektów gry: ludzi, korty i piętra. Jednak aby makiety działały, każdemu obiektowi trzeba powiedzieć, czy same obiekty są w trybie demonstracyjnym, czy po prostu narysowano ramkę. To nie jest zbyt wygodne.
Wydaje mi się, że trzeba przemyśleć architekturę, aby pojawiły się pewne ograniczenia:
- posiadanie encji, która reprezentuje i aktualizuje siebie samą, jest problemem, ponieważ ta encja nie będzie mogła "dowiedzieć się", co powinna renderować — obraz lub makietę;
- brak narzędzia do wymiany właściwości i zachowań między poszczególnymi encjami (przykład — właściwość is_build_mode lub renderowanie zachowania). Można by użyć dziedziczenia, chociaż w Rust nie ma sensownego sposobu jego realizacji. To, czego naprawdę potrzebowałem, to kompozycja;
- narzędzie do interakcji między encjami było potrzebne, aby przypisywać ludzi do kortów;
- same encje były mieszanką danych i logiki, co bardzo szybko wymykało się spod kontroli.
Przeprowadziłem dodatkowe badania i odkryłem architekturę , która jest zwykle stosowana w grach. Oto zalety ECS:
- dane są oddzielone od logiki;
- kompozycja zamiast dziedziczenia;
- architektura skupiona na danych.
Dla ECS charakterystyczne są trzy podstawowe koncepcje:
- encje — typ obiektu, na który odnosi się identyfikator (może to być gracz, piłka lub coś innego);
- komponenty — z czego składają się encje. Przykład — komponent renderowania, położenia i inne. To magazyny danych;
- systemy — korzystają zarówno z obiektów, jak i komponentów, a także zawierają zachowania i logikę, które opierają się na tych danych. Przykład — system renderowania, który iteruje przez wszystkie encje z komponentami do renderowania i zajmuje się ich rysowaniem.
Po badaniach stało się jasne, że ECS rozwiązuje takie problemy:
- zastosowanie kompozycji zamiast dziedziczenia dla systemowej organizacji encji;
- pozbycie się bałaganu w kodzie dzięki systemom zarządzania;
- użycie metod takich jak is_build_mode, aby przechowywać logikę makiety w tym samym miejscu — w systemie renderowania.
Oto co wyszło po wprowadzeniu ECS.
resources -> to tutaj znajdują się wszystkie zasoby (obrazy)
src
— komponenty
— position.rs
— person.rs
— tennis_court.rs
— floor.rs
— wireframe.rs
— mouse_tracked.rs
— zasoby
— mouse.rs
— systemy
— rendering.rs
— constants.rs
— utils.rs
— world_factory.rs -> funkcje fabryki świata
— main.rs -> główna pętla
Przydzielanie ludzi na korty
ECS uprościło życie. Teraz miałem systematyczny sposób dodawania danych do encji oraz dodawania logiki opartej na tych danych. To z kolei umożliwiło zorganizowanie rozmieszczenia ludzi na kortach.
Co zrobiłem:
- dodałem dane o przydzielonych kortach do Osoby;
- dodałem dane o rozmieszczonych ludziach do Kortu;
- dodałem SystemWybieraniaKortów, który umożliwia analizowanie ludzi i obiektów, wykrywanie dostępnych kortów i rozmieszczanie graczy na nich;
- dodałem system RuchuOsób, który wyszukuje ludzi przydzielonych na korty, a jeśli ich tam nie ma, to wysyła ich tam, gdzie są potrzebni.

Podsumowując
Bardzo podobało mi się pracować nad tą prostą grą. Co więcej, cieszę się, że użyłem do jej napisania Rust, ponieważ:
- Rust daje to, co potrzebne;
- ma świetną dokumentację, Rust jest bardzo elegancki;
- konsekwencja to jest to;
- nie ma potrzeby sięgać po klonowanie, kopiowanie ani nic podobnego, co często robiłem w C++;
- Options są bardzo wygodne w użyciu, doskonale radzą sobie z błędami;
- jeśli projekt udało się skompilować, w 99% przypadków działa tak, jak powinien. Uważam, że komunikaty błędów kompilatora są najlepsze, jakie widziałem.
Rozwój gier w Rust dopiero się zaczyna. Jednak już istnieje stabilna i dość duża społeczność, które pracuje nad tym, by uczynić Rust dostępnym dla wszystkich. Dlatego patrzę w przyszłość tego języka z optymizmem, z niecierpliwością oczekując wyników naszej wspólnej pracy.
Skillbox poleca:
- Kurs online .
- Praktyczny kurs .
- Praktyczny roczny kurs .
Źródło: habr.com
