Spela Rust på 24 timmar: erfarenhet av personlig utveckling

Spela Rust på 24 timmar: erfarenhet av personlig utveckling

I den här artikeln kommer jag att prata om min personliga erfarenhet av att utveckla ett litet spel i Rust. Det tog ungefär 24 timmar att skapa en fungerande version (jag jobbade mest på kvällar eller helger). Spelet är långt ifrån klart, men jag tror att upplevelsen kommer att vara givande. Jag ska dela med mig av vad jag lärde mig och några observationer jag gjorde när jag byggde spelet från grunden.

Skillbox rekommenderar: Tvåårig praktisk kurs "Jag är en PRO webbutvecklare".

Påminnelse: för alla läsare av "Habr" - en rabatt på 10 000 rubel när du anmäler dig till någon Skillbox-kurs med hjälp av "Habr"-kampanjkoden.

Varför rost?

Jag valde det här språket för att jag har hört mycket bra om det och jag ser att det blir mer och mer populärt inom spelutveckling. Innan jag skrev spelet hade jag liten erfarenhet av att utveckla enkla applikationer i Rust. Detta var precis tillräckligt för att ge mig en känsla av frihet när jag skrev spelet.

Varför spelet och vilken typ av spel?

Att göra spel är kul! Jag önskar att det fanns fler anledningar, men för "hem"-projekt väljer jag ämnen som inte är alltför nära relaterade till mitt vanliga arbete. Vilket spel är det här? Jag ville göra något som en tennissimulator som kombinerar Cities Skylines, Zoo Tycoon, Prison Architect och själva tennisen. I allmänhet visade det sig vara ett spel om en tennisakademi dit folk kommer för att spela.

Teknisk träning

Jag ville använda Rust, men jag visste inte exakt hur mycket grundarbete det skulle krävas för att komma igång. Jag ville inte skriva pixelshaders och använda drag-n-drop, så jag letade efter de mest flexibla lösningarna.

Jag hittade användbara resurser som jag delar med dig:

Jag utforskade flera Rust-spelmotorer och valde slutligen Piston och ggez. Jag stötte på dem när jag arbetade med ett tidigare projekt. Till slut valde jag ggez eftersom det verkade mer lämpligt för att implementera ett litet 2D-spel. Pistons modulära struktur är för komplex för en nybörjarutvecklare (eller någon som arbetar med Rust för första gången).

Spelstruktur

Jag ägnade lite tid åt att fundera över projektets arkitektur. Det första steget är att göra "land", människor och tennisbanor. Folk måste flytta runt på domstolarna och vänta. Spelare måste ha färdigheter som förbättras med tiden. Dessutom borde det finnas en editor som låter dig lägga till nya personer och domstolar, men detta är inte längre gratis.

Efter att ha tänkt igenom allt så började jag jobba.

Spelskapande

Början: Cirklar och abstraktioner

Jag tog ett exempel från ggez och fick en cirkel på skärmen. Underbar! Nu några abstraktioner. Jag tänkte att det skulle vara trevligt att abstrahera bort från idén om ett spelobjekt. Varje objekt måste renderas och uppdateras enligt beskrivningen här:

// 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(())
    }
}

Denna kodbit gav mig en fin lista med objekt som jag kunde uppdatera och rendera i en lika fin loop.

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 är nödvändigt eftersom det innehåller alla kodrader. Jag tillbringade lite tid med att separera filerna och optimera katalogstrukturen. Så här såg det ut efter det:
resurser -> det är här alla tillgångar finns (bilder)
src
- enheter
— game_object.rs
— cirkel.rs
— main.rs -> huvudslinga

Människor, golv och bilder

Nästa steg är att skapa ett personspelobjekt och ladda bilder. Allt ska byggas på basis av 32*32 brickor.

Spela Rust på 24 timmar: erfarenhet av personlig utveckling

Tennisbanor

Efter att ha studerat hur tennisbanor ser ut bestämde jag mig för att göra dem av 4*2 brickor. Från början var det möjligt att göra en bild av denna storlek, eller att sätta ihop 8 separata brickor. Men så insåg jag att det bara behövdes två unika brickor, och här är varför.

Totalt har vi två sådana brickor: 1 och 2.

Varje sektion av banan består av bricka 1 eller bricka 2. De kan läggas ut som vanligt eller vändas 180 grader.

Spela Rust på 24 timmar: erfarenhet av personlig utveckling

Grundläggande konstruktion (montering) läge

Efter att jag lyckats åstadkomma rendering av sajter, personer och kartor insåg jag att det också behövdes ett grundläggande monteringsläge. Jag implementerade det så här: när knappen trycks ned väljs objektet och klicket placerar det på önskad plats. Så, knapp 1 låter dig välja en bana och knapp 2 låter dig välja en spelare.

Men vi måste fortfarande komma ihåg vad 1 och 2 betyder, så jag lade till en trådram för att göra det tydligt vilket objekt som valdes. Så här ser det ut.

Spela Rust på 24 timmar: erfarenhet av personlig utveckling

Arkitektur och refactoring frågor

Nu har jag flera spelobjekt: människor, banor och golv. Men för att wireframes ska fungera måste varje objektenhet få veta om själva objekten är i demonstrationsläge eller om en ram helt enkelt ritas. Detta är inte särskilt bekvämt.

Det verkade för mig som att arkitekturen behövde tänkas om på ett sätt som avslöjade några begränsningar:

  • Att ha en enhet som renderar och uppdaterar sig själv är ett problem eftersom den enheten inte kommer att kunna "veta" vad den ska rendera - en bild och en wireframe;
  • avsaknad av ett verktyg för att utbyta egenskaper och beteende mellan enskilda enheter (till exempel egenskapen is_build_mode eller beteenderendering). Det skulle vara möjligt att använda arv, även om det inte finns något riktigt sätt att implementera det i Rust. Det jag verkligen behövde var layouten;
  • ett verktyg för interaktion mellan enheter behövdes för att hänvisa människor till domstolar;
  • själva enheterna var en blandning av data och logik som snabbt kom utom kontroll.

Jag gjorde lite mer forskning och upptäckte arkitekturen ECS - Entity Component System, som ofta används i spel. Här är fördelarna med ECS:

  • data separeras från logik;
  • sammansättning istället för arv;
  • datacentrerad arkitektur.

ECS kännetecknas av tre grundläggande begrepp:

  • entiteter - den typ av objekt som identifieraren refererar till (det kan vara en spelare, en boll eller något annat);
  • komponenter - enheter består av dem. Exempel - renderingskomponent, platser och annat. Dessa är datalager;
  • system - de använder både objekt och komponenter, plus innehåller beteende och logik som är baserade på dessa data. Ett exempel är ett renderingssystem som itererar genom alla enheter med renderingskomponenter och gör renderingen.

Efter att ha studerat det blev det klart att ECS löser följande problem:

  • använda layout istället för arv för att organisera enheter systemiskt;
  • bli av med kodvirrande genom kontrollsystem;
  • använder metoder som is_build_mode för att hålla trådramslogiken på samma plats - i renderingssystemet.

Detta är vad som hände efter implementering av ECS.

resurser -> det är här alla tillgångar finns (bilder)
src
- komponenter
—position.rs
— person.rs
— tennis_court.rs
— våning.rs
- wireframe.rs
— mouse_tracked.rs
- Resurser
—mus.rs
- system
— rendering.rs
— konstanter.rs
— utils.rs
— world_factory.rs -> världens fabriksfunktioner
— main.rs -> huvudslinga

Vi utser människor till domstolarna

ECS har gjort livet enklare. Nu hade jag ett systematiskt sätt att lägga till data till entiteter och lägga till logik baserat på dessa data. Och detta gjorde det i sin tur möjligt att organisera fördelningen av människor mellan domstolarna.

Vad har jag gjort:

  • lagt till data om tilldelade domstolar till Person;
  • lagt till data om distribuerade personer till TennisCourt;
  • lagt till CourtChoosingSystem, som låter dig analysera människor och banor, upptäcka tillgängliga banor och distribuera spelare till dem;
  • lagt till ett PersonMovementSystem, som letar efter personer som är tilldelade domstolarna, och om de inte är där, skickar folk dit de behöver vara.

Spela Rust på 24 timmar: erfarenhet av personlig utveckling

Sammanfattningsvis

Jag gillade verkligen att arbeta med detta enkla spel. Dessutom är jag glad att jag använde Rust för att skriva det, eftersom:

  • Rost ger dig det du behöver;
  • den har utmärkt dokumentation, Rust är ganska elegant;
  • konsistensen är cool;
  • du behöver inte ta till kloning, kopiering eller andra liknande åtgärder, vilket jag ofta gjorde i C++;
  • Alternativen är mycket enkla att använda och hanterar fel mycket bra;
  • om projektet kunde sammanställas, så fungerar det 99 % av tiden, och exakt som det ska. Jag tror att kompilatorns felmeddelanden är de bästa jag har sett.

Spelutvecklingen i Rust har bara börjat. Men det finns redan ett stabilt och ganska stort community som arbetar för att öppna Rust för alla. Därför ser jag på språkets framtid med optimism och ser fram emot resultatet av vårt gemensamma arbete.

Skillbox rekommenderar:

Källa: will.com

Lägg en kommentar