Gioco su Rust in 24 ore: esperienza personale di sviluppo

Gioco su Rust in 24 ore: esperienza personale di sviluppo

In questo articolo condividerò la mia esperienza nello sviluppo di un piccolo gioco su Rust. La creazione della versione funzionante ha richiesto circa 24 ore (principalmente lavoravo di sera o nei fine settimana). Il gioco è ancora lontano dal completamento, ma penso che l'esperienza sarà utile. Racconterò ciò che ho imparato e alcune osservazioni fatte durante la costruzione del gioco da zero.

Skillbox consiglia: Corso pratico di due anni «Io sono un sviluppatore web PRO».

Ricordiamo: per tutti i lettori di «Habr» — sconto di 10.000 rubli per l'iscrizione a qualsiasi corso Skillbox con il codice promozionale «Habr».

Perché Rust?

Ho scelto questo linguaggio perché ne ho sentito parlare molto bene e vedo che sta diventando sempre più popolare nel campo dello sviluppo di giochi. Prima di scrivere il gioco, avevo un'esperienza limitata nello sviluppo di semplici applicazioni su Rust. Questo è stato sufficiente per sentire una certa libertà durante la scrittura del gioco.

Perché un gioco e che tipo di gioco?

Creare giochi è divertente! Vorrei avere più motivi per farlo, ma per i progetti "casalinghi" scelgo temi che non sono troppo legati al mio lavoro consueto. Che gioco è? Volevo realizzare qualcosa come un simulatore di tennis, dove si fondono Cities Skylines, Zoo Tycoon, Prison Architect e, ovviamente, il tennis. In breve, è venuto fuori un gioco su un'accademia di tennis, dove le persone vengono a giocare.

Formazione tecnica

Volevo utilizzare Rust, ma non sapevo esattamente quanto fosse necessario partire da zero. Non volevo scrivere shader pixel e usare drag-and-drop, quindi cercavo le soluzioni più flessibili.

Ho trovato risorse utili che voglio condividere con voi:

Ho esaminato alcuni motori di gioco in Rust, scegliendo infine Piston e ggez. Li avevo già incontrati lavorando al progetto precedente. Alla fine, ho scelto ggez, poiché mi è sembrato il più adatto per realizzare un piccolo gioco 2D. La struttura modulare di Piston è troppo complessa per un sviluppatore alle prime armi (o per chi lavora per la prima volta con Rust).

Struttura del gioco

Ho dedicato un po' di tempo a riflettere sull'architettura del progetto. Il primo passo è creare il 'terreno', le persone e i campi da tennis. Le persone devono muoversi sui campi e aspettare. I giocatori devono avere abilità che si affinano nel tempo. Inoltre, deve esserci un editor che consenta di aggiungere nuove persone e campi, ma questo non sarà gratuito.

Dopo aver riflettuto su tutto, ho iniziato a lavorare.

Creazione di un gioco

Inizio: cerchi e astrazioni

Ho preso spunto da ggez e ho visualizzato un cerchio sullo schermo. Straordinario! Ora un po' di astrazioni. Ho pensato che fosse una buona idea astrarre il concetto di oggetto di gioco. Ogni oggetto deve essere reso e aggiornato come indicato qui:

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

Questo frammento di codice mi ha permesso di ottenere un ottimo elenco di oggetti che posso aggiornare e rendere in un ciclo altrettanto valido.

mpl event::EventHandler for MainState {
    fn update(&mut self, context: &mut Context) -> GameResult {
        // Aggiorna tutti gli oggetti
        for object in self.objects.iter_mut() {
            object.update(context)?;
        }
 
        Ok(())
    }
 
    fn draw(&mut self, context: &mut Context) -> GameResult {
        graphics::clear(context);
 
        // Disegna tutti gli oggetti
        for object in self.objects.iter_mut() {
            object.draw(context)?;
        }
 
        graphics::present(context);
 
        Ok(())
    }
}

main.rs è necessario perché contiene tutte le righe di codice. Ho impiegato un po' di tempo a suddividere i file e ottimizzare la struttura delle directory. Ecco come è apparso tutto dopo questo:
resources -> qui si trovano tutte le risorse (immagini)
src
— entità
— game_object.rs
— circle.rs
— main.rs -> ciclo principale

Persone, piani e immagini

La fase successiva consiste nella creazione dell'oggetto di gioco Person e nel caricamento delle immagini. Tutto deve basarsi su tasselli delle dimensioni 32*32.

Gioco su Rust in 24 ore: esperienza personale di sviluppo

Campi da tennis

Dopo aver studiato l'aspetto dei campi da tennis, ho deciso di realizzarli utilizzando tasselli di 4*2. Inizialmente avrei potuto creare un'immagine di tale dimensione o comporre insieme 8 tasselli separati. Ma poi ho capito che erano necessarie solo due tessere uniche, ecco perché.

In totale abbiamo due di queste tessere: 1 e 2.

Ogni sezione del campo è composta da tessera 1 o tessera 2. Possono essere disposte normalmente o capovolte di 180 gradi.

Gioco su Rust in 24 ore: esperienza personale di sviluppo

Modalità di costruzione principale

Dopo aver ottenuto il rendering delle aree, delle persone e delle mappe, ho capito che era necessario implementare anche una modalità di costruzione di base. L'ho realizzata in questo modo: quando si preme un pulsante, l'oggetto viene selezionato e un clic lo posiziona nel punto desiderato. Così, il pulsante 1 consente di scegliere il campo, mentre il pulsante 2 permette di selezionare il giocatore.

Ma dobbiamo anche ricordare cosa significano 1 e 2, quindi ho aggiunto un wireframe per rendere chiaro quale oggetto è selezionato. Ecco come appare.

Gioco su Rust in 24 ore: esperienza personale di sviluppo

Domande sull'architettura e sul refactoring

Ora ho diversi oggetti di gioco: persone, campi e piani. Tuttavia, affinché i wireframe funzionino, ogni entità dell'oggetto deve informare se gli oggetti stessi sono in modalità di dimostrazione o se è semplicemente disegnata una cornice. Questo non è molto conveniente.

Mi è sembrato necessario ripensare l'architettura in modo da evidenziare alcune limitazioni:

  • la presenza di un'entità che si mostra e si aggiorna da sola è un problema, poiché tale entità non sarà in grado di 'sapere' cosa deve rendere: immagine e wireframe;
  • l'assenza di uno strumento per scambiare proprietà e comportamenti tra entità separate (ad esempio, la proprietà is_build_mode o il rendering del comportamento). Si potrebbe utilizzare l'ereditarietà, anche se in Rust non c'è un modo corretto per implementarla. Quello di cui avevo realmente bisogno era la composizione;
  • uno strumento per far interagire le entità tra loro era necessario per assegnare persone ai campi;
  • le entità stesse erano una miscela di dati e logica, il che rapidamente sfuggiva al controllo.

Ho condotto ulteriori ricerche e ho scoperto l'architettura ECS — Entity Component System, che viene comunemente utilizzata nei giochi. Ecco i vantaggi dell'ECS:

  • i dati sono separati dalla logica;
  • composizione invece di ereditarietà;
  • un'architettura orientata ai dati.

Per l'ECS ci sono tre concetti di base:

  • entità — un tipo di oggetto a cui fa riferimento un identificatore (può essere un giocatore, una palla o altro);
  • componenti — di cui sono composte le entità. Esempio — componente di rendering, posizione e altri. Questi sono archivi di dati;
  • I sistemi utilizzano sia oggetti che componenti, e contengono comportamenti e logiche basati su questi dati. Un esempio è un sistema di rendering che esamina tutte le entità con componenti per il rendering e si occupa della visualizzazione.

Dopo aver approfondito, è diventato chiaro che l'ECS risolve problemi come:

  • l'uso della composizione invece dell'ereditarietà per organizzare sistematicamente le entità;
  • la semplificazione del codice tramite sistemi di gestione;
  • l'uso di metodi come is_build_mode, per mantenere la logica del wireframe nello stesso luogo: nel sistema di rendering.

Ecco il risultato dopo l'introduzione dell'ECS.

resources -> qui si trovano tutte le risorse (immagini)
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 -> funzioni del world factory
— main.rs -> ciclo principale

Assegniamo le persone ai campi

L'ECS ha semplificato la vita. Ora avevo un modo sistematico per aggiungere dati alle entità e per aggiungere logica basata su questi dati. Questo, a sua volta, ha consentito di organizzare la distribuzione delle persone sui campi.

Cosa ho fatto:

  • ho aggiunto dati sui campi assegnati in Person;
  • ho aggiunto dati sulle persone assegnate in TennisCourt;
  • ho aggiunto il CourtChoosingSystem, che consente di analizzare persone e strutture, scoprire i campi disponibili e assegnare i giocatori a essi;
  • ho integrato il sistema PersonMovementSystem, che cerca le persone assegnate ai campi e, se non ci sono, le invia dove necessario.

Gioco su Rust in 24 ore: esperienza personale di sviluppo

Tiriamo le somme

Mi è piaciuto molto lavorare su questo semplice gioco. Inoltre, sono soddisfatta di aver utilizzato Rust per scriverlo, poiché:

  • Rust ti fornisce ciò di cui hai bisogno;
  • ha una documentazione eccellente e Rust è molto elegante;
  • la costanza è fantastica;
  • non devo ricorrere a clonazione, copia o altre azioni simili, cosa che spesso facevo in C++;
  • Options sono molto pratiche da usare e gestiscono anche bene gli errori;
  • se il progetto riesce a compilarsi, nel 99% dei casi funziona esattamente come dovrebbe. I messaggi di errore del compilatore sono, a mio avviso, i migliori che abbia mai visto.

Lo sviluppo di giochi in Rust è appena iniziato. Ma c'è già una comunità stabile e piuttosto ampia che lavora per rendere Rust accessibile a tutti. Pertanto, guardo al futuro del linguaggio con ottimismo, aspettandomi con impazienza i risultati del nostro lavoro comune.

Skillbox consiglia:

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, server VPS VDS 🔥 Acquista hosting affidabile per siti web con protezione DDoS, server VPS VDS | ProHoster