Spielentwicklung in Rust innerhalb von 24 Stunden: persönliche Erfahrungen

Spielentwicklung in Rust innerhalb von 24 Stunden: persönliche Erfahrungen

In diesem Artikel berichte ich von meinen persönlichen Erfahrungen bei der Entwicklung eines kleinen Spiels in Rust. Die Erstellung einer funktionierenden Version hat etwa 24 Stunden in Anspruch genommen (ĂŒberwiegend habe ich abends oder am Wochenende gearbeitet). Das Spiel ist noch lange nicht fertig, aber ich denke, dass die Erfahrung nĂŒtzlich sein wird. Ich werde erzĂ€hlen, was ich gelernt habe, und einige Beobachtungen teilen, die ich beim Aufbau des Spiels von Grund auf gemacht habe.

Skillbox empfiehlt: ZweijĂ€hriger praktischer Kurs „Ich bin PRO-Webentwickler“.

Wir erinnern daran: alle Leser von „Habr“ erhalten einen Rabatt von 10.000 Rubel bei der Anmeldung zu einem beliebigen Kurs von Skillbox mit dem Aktionscode „Habr“.

Warum Rust?

Ich habe diese Sprache gewĂ€hlt, weil ich viel Gutes darĂŒber gehört habe und sehe, dass sie in der Spieleentwicklung immer populĂ€rer wird. Vor der Spieleentwicklung hatte ich ein wenig Erfahrung in der Erstellung einfacher Anwendungen in Rust. Das war gerade genug, um ein gewisses Maß an Freiheit beim Schreiben des Spiels zu erleben.

Warum ein Spiel und welches Spiel?

Spiele zu entwickeln macht Spaß! Ich wĂŒnschte, es gĂ€be mehr GrĂŒnde, aber fĂŒr „hĂ€usliche“ Projekte wĂ€hle ich Themen, die nicht zu eng mit meiner regulĂ€ren Arbeit verbunden sind. Welches Spiel? Ich wollte etwas in der Art eines Tennissimulators schaffen, der Cities Skylines, Zoo Tycoon, Prison Architect und eben Tennis vereint. Insgesamt handelt es sich um ein Spiel ĂŒber eine Tennisschule, zu der Leute zum Spielen kommen.

Technische Vorbereitung

Ich wollte Rust verwenden, wusste aber nicht genau, wie sehr ich bei Null anfangen mĂŒsste. Ich wollte keine Pixel-Shader schreiben und Drag-and-Drop verwenden, also suchte ich nach den flexibelsten Lösungen.

Ich fand nĂŒtzliche Ressourcen, die ich mit Ihnen teile:

Ich habe mir mehrere Rust-Spiel-Engines angesehen und mich schließlich fĂŒr Piston und ggez entschieden. Mit diesen hatte ich bereits bei einem vorherigen Projekt gearbeitet. Letztendlich wĂ€hlte ich ggez, da es sich als passender fĂŒr die Umsetzung eines kleinen 2D-Spiels anbot. Die modulare Struktur von Piston ist fĂŒr einen AnfĂ€nger (oder jemanden, der zum ersten Mal mit Rust arbeitet) zu komplex.

Die Struktur des Spiels

Ich habe etwas Zeit damit verbracht, ĂŒber die Architektur des Projekts nachzudenken. Der erste Schritt ist, „Land“, Menschen und TennisplĂ€tze zu schaffen. Die Menschen sollen sich ĂŒber die PlĂ€tze bewegen und warten. Die Spieler sollten FĂ€higkeiten haben, die sich im Laufe der Zeit verbessern. Außerdem sollte es einen Editor geben, der es ermöglicht, neue Menschen und PlĂ€tze hinzuzufĂŒgen, aber das ist nicht kostenlos.

Nachdem ich alles durchdacht hatte, begann ich mit der Arbeit.

Spielerstellung

Beginn: Kreise und Abstraktionen

Ich habe ein Beispiel aus ggez genommen und einen Kreis auf dem Bildschirm erhalten. Unglaublich! Jetzt ein wenig Abstraktionen. Ich dachte, es wÀre gut, von der Idee eines Spielobjekts abzusehen. Jedes Objekt sollte gerendert und aktualisiert werden, wie hier angegeben:

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

Dieser Codeabschnitt hat es mir ermöglicht, eine tolle Liste von Objekten zu erhalten, die ich in einem nicht weniger großartigen Zyklus aktualisieren und rendern kann.

mpl event::EventHandler fĂŒr MainState {
    fn update(&mut self, context: &mut Context) -> GameResult {
        // Aktualisiere alle Objekte
        for object in self.objects.iter_mut() {
            object.update(context)?;
        }
 
        Ok(())
    }
 
    fn draw(&mut self, context: &mut Context) -> GameResult {
        graphics::clear(context);
 
        // Zeichne alle Objekte
        for object in self.objects.iter_mut() {
            object.draw(context)?;
        }
 
        graphics::present(context);
 
        Ok(())
    }
}

main.rs ist notwendig, da dort alle Codezeilen enthalten sind. Ich habe etwas Zeit damit verbracht, die Dateien zu trennen und die Verzeichnisstruktur zu optimieren. So sieht es danach aus:
resources -> hier sind alle Assets (Bilder)
src
— entities
— game_object.rs
— circle.rs
— main.rs -> Hauptschleife

Menschen, PlÀtze und Bilder

Der nÀchste Schritt ist die Erstellung des Spielobjekts Person und das Laden der Bilder. Alles sollte auf 32*32 Kacheln basieren.

Spielentwicklung in Rust innerhalb von 24 Stunden: persönliche Erfahrungen

TennisplÀtze

Nachdem ich untersucht hatte, wie TennisplĂ€tze aussehen, entschied ich mich, sie aus 4*2 Kacheln zu erstellen. ZunĂ€chst hĂ€tte man ein Bild in dieser GrĂ¶ĂŸe erstellen oder 8 separate Kacheln zusammenstellen können. Aber dann erkannte ich, dass nur zwei einzigartige Kacheln benötigt werden, und hier ist der Grund.

Insgesamt haben wir zwei solche Kacheln: 1 und 2.

Jeder Abschnitt des Platzes besteht aus Kachel 1 oder Kachel 2. Sie können normal angeordnet oder um 180 Grad gedreht sein.

Spielentwicklung in Rust innerhalb von 24 Stunden: persönliche Erfahrungen

Hauptmodus des Bauens (Zusammenbau)

Nachdem ich die Anzeige der PlĂ€tze, Menschen und Karten erreicht hatte, wurde mir klar, dass auch ein grundlegender Zusammenbaubereich erforderlich ist. Ich habe ihn so implementiert: Wenn die Taste gedrĂŒckt wird, wird das Objekt ausgewĂ€hlt, und ein Klick platziert es an der gewĂŒnschten Stelle. So ermöglicht die Taste 1 die Auswahl des Platzes, wĂ€hrend die Taste 2 die Auswahl des Spielers erlaubt.

Aber ich muss auch im GedĂ€chtnis behalten, was 1 und 2 bedeutet, also habe ich ein Wireframe hinzugefĂŒgt, um deutlich zu machen, welches Objekt ausgewĂ€hlt ist. So sieht es aus.

Spielentwicklung in Rust innerhalb von 24 Stunden: persönliche Erfahrungen

Fragen zur Architektur und zum Refactoring

Jetzt habe ich mehrere Spielobjekte: Menschen, PlÀtze und Etagen. Aber damit die Wireframes funktionieren, muss jeder EntitÀt des Objekts mitgeteilt werden, ob die Objekte im Demomodus oder nur mit einem Rahmen dargestellt werden. Das ist nicht ganz praktisch.

Mir schien, dass die Architektur so neu durchdacht werden muss, dass einige EinschrÀnkungen deutlich werden:

  • Das Vorhandensein einer EntitĂ€t, die sich selbst darstellt und aktualisiert, ist ein Problem, da diese EntitĂ€t nicht „erkennen“ kann, was sie rendern soll – Bild oder Wireframe;
  • Das Fehlen eines Werkzeugs zum Austausch von Eigenschaften und Verhalten zwischen einzelnen EntitĂ€ten (z. B. die Eigenschaft is_build_mode oder das Rendern von Verhalten). Man könnte Vererbung verwenden, obwohl es in Rust keinen normalen Weg zur Implementierung gibt. Was ich wirklich brauchte, war Komposition;
  • Ein Werkzeug zur Interaktion der EntitĂ€ten miteinander war nötig, um Personen den PlĂ€tzen zuzuweisen;
  • Die EntitĂ€ten selbst stellten eine Mischung aus Daten und Logik dar, was sehr schnell außer Kontrolle geriet.

Ich habe eine zusĂ€tzliche Untersuchung durchgefĂŒhrt und die Architektur entdeckt ECS – Entity Component System, die normalerweise in Spielen verwendet wird. Hier sind die Vorteile von ECS:

  • Daten sind von Logik getrennt;
  • Komposition statt Vererbung;
  • Datenzentrierte Architektur.

FĂŒr ECS sind drei grundlegende Konzepte charakteristisch:

  • EntitĂ€ten – eine Art Objekt, auf das sich die Identifikation bezieht (das kann ein Spieler, ein Ball oder etwas anderes sein);
  • Komponenten – aus denen die EntitĂ€ten bestehen. Beispiel – Rendering-, Positions- und andere Komponenten. Dies sind Datenspeicher;
  • Systeme – sie verwenden sowohl Objekte als auch Komponenten und enthalten Verhalten und Logik, die auf diesen Daten basieren. Beispiel – ein Rendering-System, das alle EntitĂ€ten mit Rendering-Komponenten durchlĂ€uft und sich um das Rendern kĂŒmmert.

Nach dem Studium wurde klar, dass ECS folgende Probleme löst:

  • Verwendung von Komposition statt Vererbung fĂŒr die systematische Organisation von EntitĂ€ten;
  • Beseitigung von Codemischung durch Verwaltungssysteme;
  • Verwendung von Methoden wie is_build_mode, um die Logik des Wireframes am selben Ort zu speichern – im Rendering-System.

So sieht es nach der Implementierung von ECS aus.

resources -> hier sind alle Assets (Bilder)
src
— Komponenten
— position.rs
— person.rs
— tennis_court.rs
— floor.rs
— wireframe.rs
— mouse_tracked.rs
— Ressourcen
— mouse.rs
— Systeme
— rendering.rs
— constants.rs
— utils.rs
— world_factory.rs -> Funktionen der Weltfabrik
— main.rs -> Hauptschleife

Wir weisen Personen den PlÀtzen zu

ECS hat das Leben einfacher gemacht. Jetzt hatte ich einen systematischen Weg, um Daten zu EntitĂ€ten hinzuzufĂŒgen und Logik basierend auf diesen Daten zu integrieren. Das ermöglicht es wiederum, die Verteilung der Personen auf den PlĂ€tzen zu organisieren.

Was ich gemacht habe:

  • Ich habe Daten ĂŒber die zugewiesenen PlĂ€tze in Person hinzugefĂŒgt;
  • Ich habe Daten ĂŒber die verteilten Personen im TennisCourt hinzugefĂŒgt;
  • Ich habe das CourtChoosingSystem hinzugefĂŒgt, das es ermöglicht, die Personen und PlĂ€tze zu analysieren, verfĂŒgbare PlĂ€tze zu finden und die Spieler darauf zu verteilen;
  • Ich habe das PersonMovementSystem hinzugefĂŒgt, das die Personen, die den PlĂ€tzen zugewiesen sind, sucht und sie, falls sie nicht dort sind, dorthin sendet, wo sie benötigt werden.

Spielentwicklung in Rust innerhalb von 24 Stunden: persönliche Erfahrungen

Zusammenfassung

Ich habe es sehr genossen, an diesem einfachen Spiel zu arbeiten. DarĂŒber hinaus bin ich froh, dass ich zur Erstellung Rust verwendet habe, da:

  • Rust gibt Ihnen, was Sie benötigen;
  • es hat eine ausgezeichnete Dokumentation, Rust ist sehr elegant;
  • Konstanz ist großartig;
  • es ist nicht nötig, Klonierung, Kopieren oder Ă€hnliche Aktionen durchzufĂŒhren, was ich oft in C++ gemacht habe;
  • Options sind sehr praktisch in der Handhabung, sie verarbeiten auch Fehler hervorragend;
  • wenn das Projekt kompiliert werden kann, dann funktioniert es in 99 % der FĂ€lle so, wie es sollte. Ich denke, die Fehlermeldungen des Compilers sind die besten, die ich je gesehen habe.

Die Spieleentwicklung mit Rust beginnt gerade erst. Aber es gibt bereits eine stabile und ziemlich große Community, die daran arbeitet, Rust fĂŒr alle zugĂ€nglich zu machen. Deshalb blicke ich optimistisch in die Zukunft der Sprache und freue mich auf die Ergebnisse unserer gemeinsamen Arbeit.

Skillbox empfiehlt:

Quelle: habr.com

60GB SSD 8Gb DDR4