
In dit artikel deel ik mijn persoonlijke ervaring met het ontwikkelen van een kleine game in Rust. Het kostte ongeveer 24 uur om een werkende versie te maken (voornamelijk werkte ik ’s avonds of in het weekend). De game is nog lang niet af, maar ik denk dat de ervaring waardevol zal zijn. Ik zal vertellen wat ik geleerd heb en enkele observaties delen die ik heb gedaan bij het bouwen van de game vanaf nul.
Skillbox raadt aan: Praktische tweejarenopleiding .
Ter herinnering: voor alle lezers van «Habr» — een korting van 10.000 roebel bij inschrijving voor elke cursus van Skillbox met de promocode «Habr».
Waarom Rust?
Ik koos deze taal omdat ik veel goeds erover had gehoord en zie dat deze steeds populairder wordt in de game-ontwikkelingssector. Voor het schrijven van de game had ik enige ervaring met de ontwikkeling van eenvoudige applicaties in Rust. Dat was precies genoeg om een bepaalde vrijheid te voelen tijdens het schrijven van de game.
Waarom juist een game en wat voor game?
Games maken is leuk! Ik zou willen dat er meer redenen waren, maar voor ‘thuis’ projecten kies ik onderwerpen die niet te nauw verbonden zijn met mijn reguliere werk. Wat voor game? Ik wilde iets maken als een tennis simulator, waarbij elementen van Cities Skylines, Zoo Tycoon, Prison Architect en tennis zelf worden gecombineerd. Kortom, het is een game over een tennisacademie waar mensen komen om te spelen.
Technische voorbereiding
Ik wilde Rust gebruiken, maar wist niet precies hoe ‘van nul’ ik moest beginnen. Ik wilde geen pixel shaders schrijven en geen drag-and-drop gebruiken, dus zocht ik naar de meest flexibele oplossingen.
Ik vond enkele nuttige bronnen die ik met jullie deel:
- — een lijst met noodzakelijke elementen voor het ontwikkelen van games in Rust;
Ik heb verschillende game engines in Rust bestudeerd en uiteindelijk gekozen voor Piston en ggez. Ik had met hen gewerkt aan een eerder project. Uiteindelijk koos ik voor ggez omdat het geschikter leek voor de implementatie van een kleine 2D-game. De modulaire structuur van Piston is te complex voor een beginnende ontwikkelaar (of iemand die voor het eerst met Rust werkt).
Structuur van de game
Ik heb wat tijd besteed aan het nadenken over de architectuur van het project. De eerste stap is om ‘grond’, mensen en tennisbanen te maken. Mensen moeten zich over de banen kunnen verplaatsen en wachten. De spelers moeten vaardigheden hebben die in de loop van de tijd verbeteren. Bovendien moet er een editor zijn waarmee nieuwe mensen en banen kunnen worden toegevoegd, maar dat is al niet gratis.
Nadat ik alles had doordacht, begon ik met het werk.
Een spel maken
Begin: cirkels en abstracties
Ik heb een voorbeeld uit ggez genomen en kreeg een cirkel op het scherm. Verbazingwekkend! Nu wat abstracties. Het leek me goed om me te abstraheren van het idee van een game-object. Elk object moet worden gerenderd en bijgewerkt, zoals hier aangegeven:
// 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(())
}
}Dit stuk code stelde me in staat om een uitstekende lijst van objecten te krijgen die ik kan bijwerken en renderen in een minimaal uitstekende lus.
mpl event::EventHandler voor MainState {
fn update(&mut self, context: &mut Context) -> GameResult {
// Werk alle objecten bij
for object in self.objects.iter_mut() {
object.update(context)?;
}
Ok(())
}
fn draw(&mut self, context: &mut Context) -> GameResult {
graphics::clear(context);
// Teken alle objecten
for object in self.objects.iter_mut() {
object.draw(context)?;
}
graphics::present(context);
Ok(())
}
} main.rs is nodig omdat het alle rijbewijsregels bevat. Ik heb wat tijd besteed aan het splitsen van bestanden en het optimaliseren van de directorystructuur. Zo zag alles eruit na deze aanpassingen:
resources -> dit is waar alle middelen zijn (afbeeldingen)
src
— entiteiten
— game_object.rs
— circle.rs
— main.rs -> hoofdloop
Mensen, banen en afbeeldingen
De volgende stap is het creëren van het game-object Persoon en het laden van afbeeldingen. Alles moet gebaseerd zijn op tegels van 32*32.

Tennisbanen
Na het bestuderen van hoe tennisbanen eruitzien, besloot ik ze te maken met tegels van 4*2. In het begin kon ik een afbeelding van die grootte maken of 8 afzonderlijke tegels samenvoegen. Maar later besefte ik dat er slechts twee unieke tegels nodig zijn, en hier is waarom.
We hebben in totaal twee van deze tegels: 1 en 2.
Elke sectie van de baan bestaat uit tegel 1 of tegel 2. Ze kunnen normaal worden geplaatst of 180 graden worden gedraaid.

De belangrijkste bouwmodus (assemblage)
Nadat ik had bereikt dat de velden, mensen en kaarten werden gerenderd, realiseerde ik me dat er ook een basisassemblagemodus nodig was. Dit implementeerde ik zo: wanneer er op de knop wordt gedrukt, wordt het object geselecteerd en de klik plaatst het op de gewenste plek. Knop 1 stelt me in staat om de baan te selecteren, terwijl knop 2 de speler kan selecteren.
Maar het is ook belangrijk om te onthouden wat 1 en 2 betekenen, dus ik voegde een wireframe toe om te laten zien welk object is geselecteerd. Zo ziet dat eruit.

Vragen over architectuur en refactoring
Nu heb ik verschillende game-objecten: mensen, tennisbanen en verdiepingen. Maar om de wireframes te laten werken, moet ik elke objectentiteit vertellen of de objecten zich in een demomodus bevinden of dat het gewoon een frame is. Dit is niet erg handig.
Het leek me nodig om de architectuur opnieuw te overdenken, zodat er enkele beperkingen naar voren komen:
- het bestaan van een entiteit die zichzelf weergeeft en bijwerkt, is een probleem, omdat deze entiteit niet kan "weten" wat ze moet renderen — afbeelding of wireframe;
- het ontbreken van een hulpmiddel om eigenschappen en gedrag uit te wisselen tussen afzonderlijke entiteiten (voorbeeld — eigenschap is_build_mode of het tekenen van gedrag). Je zou overerving kunnen gebruiken, hoewel er in Rust geen goede manier is om dit te implementeren. Wat ik echt nodig had, was compositie;
- een hulpmiddel om de interactie tussen entiteiten mogelijk te maken, was nodig om mensen aan de tennisbanen toe te wijzen;
- de entiteiten bestonden uit een mix van gegevens en logica, wat al snel buiten controle raakte.
Ik heb verder onderzoek gedaan en ontdekte de architectuur , die doorgaans in games wordt gebruikt. Hier zijn de voordelen van ECS:
- gegevens zijn gescheiden van logica;
- compositie in plaats van overerving;
- een architectuur die op gegevens is gericht.
ECS heeft drie basale concepten:
- entiteiten — een type object waar een identificator naar verwijst (dit kan een speler, een bal of iets anders zijn);
- componenten — waaruit entiteiten bestaan. Voorbeeld — renderingcomponent, positionering en andere. Dit zijn gegevensopslagplaatsen;
- systemen — ze gebruiken zowel objecten als componenten, plus bevatten gedrag en logica die op deze gegevens zijn gebaseerd. Voorbeeld — een renderingsysteem dat door alle entiteiten met renderingscomponenten gaat en zich bezighoudt met de weergave.
Na het bestuderen werd het duidelijk dat ECS de volgende problemen oplost:
- toepassing van compositie in plaats van overerving voor de systeemsorganisatie van entiteiten;
- ontsporing van rommelige code door beheersystemen;
- gebruik van methoden zoals is_build_mode om de logica van het wireframe op dezelfde plaats te bewaren — in het renderingsysteem.
Dit is wat eruit kwam na de implementatie van ECS.
resources -> dit is waar alle middelen zijn (afbeeldingen)
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 -> wereldfabriekfuncties
— main.rs -> hoofdloop
We wijzen mensen toe aan de tennisbanen
ECS heeft het leven gemakkelijker gemaakt. Nu had ik een systeem om gegevens aan entiteiten toe te voegen en logica op basis van deze gegevens toe te voegen. Dit stelde me in staat om de verdeling van mensen over de tennisbanen te organiseren.
Wat ik heb gedaan:
- ik heb gegevens over toegewezen banen aan Person toegevoegd;
- ik heb gegevens over verdeelde mensen aan TennisCourt toegevoegd;
- ik heb CourtChoosingSystem toegevoegd, dat mensen en locaties analyseert, beschikbare banen ontdekt en spelers erop verdeelt;
- ik heb PersonMovementSystem toegevoegd, dat mensen zoekt die aan banen zijn toegewezen, en als ze daar niet zijn, ze naar de juiste locatie stuurt.

Laten we de balans opmaken
Ik heb erg veel genoten van het werken aan dit eenvoudige spel. Bovendien ben ik blij dat ik Rust heb gebruikt om het te schrijven, omdat:
- Rust geeft je wat je nodig hebt;
- het heeft uitstekende documentatie, Rust is erg elegant;
- de stabiliteit is geweldig;
- je hoeft geen klonen, kopieën of soortgelijke acties te ondernemen, wat ik vaak in C++ deed;
- Options zijn zeer handig om mee te werken, ze verwerken ook fouten perfect;
- als het project succesvol is gecompileerd, werkt het in 99% van de gevallen zoals het hoort. De foutmeldingen van de compiler zijn naar mijn mening de beste die ik heb gezien.
Game-ontwikkeling in Rust staat nog in de kinderschoenen. Maar er is al een stabiele en redelijk grote community die werkt aan het toegankelijk maken van Rust voor iedereen. Daarom kijk ik optimistisch naar de toekomst van de taal, in afwachting van de resultaten van ons gezamenlijke werk.
Skillbox raadt aan:
- Online cursus .
- Praktische cursus .
- Praktische jaaropleiding .
Bron: habr.com
