Xogando a Rust en 24 horas: experiencia de desenvolvemento persoal

Xogando a Rust en 24 horas: experiencia de desenvolvemento persoal

Neste artigo falarei da miña experiencia persoal ao desenvolver un pequeno xogo en Rust. Tardaron unhas 24 horas en crear unha versión de traballo (traballei principalmente polas noites ou os fins de semana). O xogo está lonxe de rematar, pero creo que a experiencia será gratificante. Vou compartir o que aprendín e algunhas observacións que fixen ao construír o xogo desde cero.

Skillbox recomenda: Curso práctico de dous anos "Son un programador web PRO".

Recordámolo: para todos os lectores de "Habr" - un desconto de 10 rublos ao inscribirse en calquera curso de Skillbox usando o código promocional "Habr".

Por que Rust?

Escollín este idioma porque escoitei moitas cousas boas sobre el e vexo que se fai cada vez máis popular no desenvolvemento de xogos. Antes de escribir o xogo, tiña pouca experiencia no desenvolvemento de aplicacións sinxelas en Rust. Isto foi o suficiente para darme unha sensación de liberdade ao escribir o xogo.

Por que o xogo e que tipo de xogo?

Facer xogos é divertido! Gustaríame que houbese máis motivos, pero para os proxectos “na casa” escollo temas que non están moi relacionados co meu traballo habitual. Que xogo é este? Quería facer algo así como un simulador de tenis que combina Cities Skylines, Zoo Tycoon, Prison Architect e o propio tenis. En xeral, resultou ser un xogo sobre unha academia de tenis onde a xente vén xogar.

Formación técnica

Quería usar Rust, pero non sabía exactamente cantos traballos básicos serían necesarios para comezar. Non quería escribir sombreadores de píxeles nin usar arrastrar e soltar, polo que buscaba as solucións máis flexibles.

Atopei recursos útiles que comparto contigo:

Explorei varios motores de xogos de Rust, e finalmente escollei Piston e ggez. Atopeime con eles mentres traballaba nun proxecto anterior. Ao final, escollín ggez porque parecía máis axeitado para implementar un pequeno xogo en 2D. A estrutura modular de Piston é demasiado complexa para un desenvolvedor novato (ou alguén que está a traballar con Rust por primeira vez).

Estrutura do xogo

Estiven un tempo pensando na arquitectura do proxecto. O primeiro paso é facer “terra”, xente e pistas de tenis. A xente ten que moverse polos xulgados e esperar. Os xogadores deben ter habilidades que melloren co paso do tempo. Ademais, debería haber un editor que che permita engadir novas persoas e tribunais, pero xa non é gratuíto.

Xa pensado todo ben, púxenme a traballar.

Creación de xogos

Inicio: círculos e abstraccións

Tomei un exemplo de ggez e saín un círculo na pantalla. Marabilloso! Agora algunhas abstraccións. Pensei que sería bo abstraerse da idea dun obxecto de xogo. Cada obxecto debe ser representado e actualizado como se indica aquí:

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

Este anaco de código deume unha boa lista de obxectos que podería actualizar e renderizar nun bucle igualmente agradable.

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 é necesario porque contén todas as liñas de código. Pasei un pouco de tempo separando os ficheiros e optimizando a estrutura de directorios. Así quedou despois:
recursos -> aquí están todos os recursos (imaxes)
src
- entidades
— game_object.rs
— círculo.rs
— main.rs -> bucle principal

Persoas, pisos e imaxes

O seguinte paso é crear un obxecto de xogo Persoa e cargar imaxes. Todo debe construírse sobre a base de tellas de 32 * 32.

Xogando a Rust en 24 horas: experiencia de desenvolvemento persoal

Pistas de tenis

Despois de estudar como son as pistas de tenis, decidín facelas a partir de tellas de 4*2. Inicialmente, era posible facer unha imaxe deste tamaño ou xuntar 8 pezas separadas. Pero entón decateime de que só se necesitaban dúas fichas únicas, e aquí está o porqué.

En total temos dúas fichas deste tipo: 1 e 2.

Cada sección da pista está formada pola tella 1 ou 2. Pódense dispor de xeito normal ou virar 180 graos.

Xogando a Rust en 24 horas: experiencia de desenvolvemento persoal

Modo de construción básica (montaxe).

Despois de conseguir a representación de sitios, persoas e mapas, decateime de que tamén se necesitaba un modo de montaxe básico. Implementei así: cando se preme o botón, o obxecto está seleccionado e o clic colócao no lugar desexado. Así, o botón 1 permítelle seleccionar unha pista e o botón 2 permítelle seleccionar un xogador.

Pero aínda temos que lembrar o que significan 1 e 2, polo que engadín un wireframe para deixar claro que obxecto seleccionou. Isto é o que parece.

Xogando a Rust en 24 horas: experiencia de desenvolvemento persoal

Cuestións de arquitectura e refactorización

Agora teño varios obxectos de xogo: persoas, pistas e pisos. Pero para que os wireframes funcionen, a cada entidade obxecto debe dicirse se os propios obxectos están en modo de demostración ou se un cadro está simplemente debuxado. Isto non é moi cómodo.

Pareceume que había que repensar a arquitectura dun xeito que revelase algunhas limitacións:

  • Ter unha entidade que se renderiza e se actualice a si mesma é un problema porque esa entidade non poderá "saber" o que se supón que debe renderizar: unha imaxe e un wireframe;
  • falta dunha ferramenta para intercambiar propiedades e comportamento entre entidades individuais (por exemplo, a propiedade is_build_mode ou a representación do comportamento). Sería posible usar a herdanza, aínda que non hai unha forma adecuada de implementala en Rust. O que realmente necesitaba era a maquetación;
  • era necesaria unha ferramenta de interacción entre entidades para asignar persoas aos tribunais;
  • as propias entidades eran unha mestura de datos e lóxica que axiña se descontrolaba.

Investiguei máis e descubrín a arquitectura ECS - Sistema de compoñentes da entidade, que se usa habitualmente nos xogos. Estes son os beneficios de ECS:

  • os datos están separados da lóxica;
  • composición en lugar de herdanza;
  • arquitectura centrada en datos.

ECS caracterízase por tres conceptos básicos:

  • entidades - o tipo de obxecto ao que se refire o identificador (pode ser un xogador, unha pelota ou outra cousa);
  • compoñentes: as entidades están formadas por eles. Exemplo: compoñente de renderizado, localizacións e outros. Trátase de almacéns de datos;
  • sistemas: usan obxectos e compoñentes, ademais de conteñen comportamento e lóxica baseados nestes datos. Un exemplo é un sistema de renderizado que itera a través de todas as entidades con compoñentes de renderizado e fai o renderizado.

Despois de estudalo, quedou claro que ECS resolve os seguintes problemas:

  • usar o deseño en lugar da herdanza para organizar as entidades de forma sistémica;
  • desfacerse do revolto de código a través dos sistemas de control;
  • usando métodos como is_build_mode para manter a lóxica wireframe no mesmo lugar - no sistema de renderizado.

Isto é o que pasou despois de implementar ECS.

recursos -> aquí están todos os recursos (imaxes)
src
- compoñentes
—posición.rs
- persoa.rs
— pista_de_tenis.rs
- piso.rs
- armazón.rs
— mouse_tracked.rs
- recursos
—rato.rs
- sistemas
— renderización.rs
— constantes.rs
— utils.rs
— world_factory.rs -> funcións de fábrica do mundo
— main.rs -> bucle principal

Asignamos persoas aos tribunais

ECS facilitou a vida. Agora tiña unha forma sistemática de engadir datos ás entidades e engadir lóxica baseada neses datos. E isto, á súa vez, permitiu organizar o reparto de persoas entre os xulgados.

O que eu fixen:

  • datos engadidos sobre tribunais asignados a Persoa;
  • engadiu datos sobre persoas distribuídas a TennisCourt;
  • engadiu CourtChoosingSystem, que permite analizar persoas e canchas, detectar canchas dispoñibles e distribuírlles xogadores;
  • engadiu un PersonMovementSystem, que busca persoas asignadas aos xulgados e, se non están alí, envía xente onde precisan estar.

Xogando a Rust en 24 horas: experiencia de desenvolvemento persoal

Resumindo

Gustoume moito traballar neste xogo sinxelo. Ademais, alégrome de que usei Rust para escribilo, porque:

  • Rust dáche o que necesitas;
  • ten unha excelente documentación, Rust é bastante elegante;
  • a consistencia é xenial;
  • non tes que recorrer á clonación, copia ou outras accións similares, que eu fixen moitas veces en C++;
  • As opcións son moi fáciles de usar e manexan moi ben os erros;
  • se o proxecto puido ser compilado, entón o 99% do tempo funciona, e exactamente como debería. Creo que as mensaxes de erro do compilador son as mellores que vin.

O desenvolvemento de xogos en Rust só comeza. Pero xa hai unha comunidade estable e bastante grande traballando para abrir Rust a todos. Por iso, miro o futuro da lingua con optimismo, agardando os resultados do noso traballo común.

Skillbox recomenda:

Fonte: www.habr.com

Engadir un comentario