Jogando Rust em 24 horas: experiência de desenvolvimento pessoal

Jogando Rust em 24 horas: experiência de desenvolvimento pessoal

Neste artigo falarei sobre minha experiência pessoal no desenvolvimento de um pequeno jogo em Rust. Demorei cerca de 24 horas para criar uma versão funcional (trabalhei principalmente à noite ou nos fins de semana). O jogo está longe de terminar, mas acho que a experiência será gratificante. Compartilharei o que aprendi e algumas observações que fiz ao construir o jogo do zero.

A Skillbox recomenda: Curso prático de dois anos "Eu sou um desenvolvedor web PRO".

Lembramos: para todos os leitores de "Habr" - um desconto de 10 rublos ao se inscrever em qualquer curso Skillbox usando o código promocional "Habr".

Por que ferrugem?

Escolhi essa linguagem porque já ouvi muitas coisas boas sobre ela e vejo que ela está se tornando cada vez mais popular no desenvolvimento de jogos. Antes de escrever o jogo, eu tinha pouca experiência no desenvolvimento de aplicativos simples em Rust. Isso foi apenas o suficiente para me dar uma sensação de liberdade enquanto escrevia o jogo.

Por que o jogo e que tipo de jogo?

Fazer jogos é divertido! Gostaria que houvesse mais motivos, mas para projetos “caseiros” escolho temas que não estão muito relacionados com o meu trabalho regular. Que jogo é este? Eu queria fazer algo como um simulador de tênis que combinasse Cities Skylines, Zoo Tycoon, Prison Architect e o próprio tênis. Em geral, acabou sendo um jogo sobre uma academia de tênis onde as pessoas vêm jogar.

Treinamento técnico

Eu queria usar o Rust, mas não sabia exatamente quanto trabalho de base seria necessário para começar. Eu não queria escrever pixel shaders e usar arrastar e soltar, então estava procurando as soluções mais flexíveis.

Encontrei recursos úteis que compartilho com você:

Explorei vários motores de jogo Rust, escolhendo finalmente Piston e ggez. Eu os encontrei enquanto trabalhava em um projeto anterior. No final, escolhi o ggez porque me pareceu mais adequado para implementar um pequeno jogo 2D. A estrutura modular do Piston é muito complexa para um desenvolvedor iniciante (ou para alguém que está trabalhando com Rust pela primeira vez).

Estrutura do jogo

Passei algum tempo pensando na arquitetura do projeto. O primeiro passo é fazer “terrenos”, pessoas e quadras de tênis. As pessoas têm que circular pelos tribunais e esperar. Os jogadores devem ter habilidades que melhoram com o tempo. Além disso, deveria haver um editor que permite adicionar novas pessoas e tribunais, mas isso não é mais gratuito.

Depois de pensar em tudo, comecei a trabalhar.

Criando um jogo

Início: Círculos e Abstrações

Peguei um exemplo do ggez e fiz um círculo na tela. Maravilhoso! Agora algumas abstrações. Achei que seria bom abstrair a ideia de objeto de jogo. Cada objeto deve ser renderizado e atualizado conforme indicado aqui:

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

Esse trecho de código me deu uma boa lista de objetos que eu poderia atualizar e renderizar em um loop igualmente bom.

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 é necessário porque contém todas as linhas de código. Passei um tempinho separando os arquivos e otimizando a estrutura de diretórios. Foi assim que ficou depois disso:
recursos -> é aqui que estão todos os ativos (imagens)
src
- entidades
-game_object.rs
- círculo.rs
— main.rs -> loop principal

Pessoas, pisos e imagens

A próxima etapa é criar um objeto de jogo Person e carregar as imagens. Tudo deve ser construído com base em peças 32*32.

Jogando Rust em 24 horas: experiência de desenvolvimento pessoal

Quadras de tenis

Depois de estudar como são as quadras de tênis, decidi fazê-las com peças 4 x 2. Inicialmente era possível fazer uma imagem deste tamanho, ou juntar 8 peças separadas. Mas então percebi que eram necessárias apenas duas peças exclusivas, e aqui está o porquê.

No total, temos duas dessas peças: 1 e 2.

Cada seção da quadra consiste no ladrilho 1 ou no ladrilho 2. Eles podem ser dispostos normalmente ou virados 180 graus.

Jogando Rust em 24 horas: experiência de desenvolvimento pessoal

Modo básico de construção (montagem)

Depois de conseguir renderizar sites, pessoas e mapas, percebi que também era necessário um modo de montagem básico. Implementei assim: quando o botão é pressionado, o objeto é selecionado e o clique o coloca no local desejado. Assim, o botão 1 permite selecionar uma quadra e o botão 2 permite selecionar um jogador.

Mas ainda precisamos lembrar o que 1 e 2 significam, então adicionei um wireframe para deixar claro qual objeto foi selecionado. Isto é o que parece.

Jogando Rust em 24 horas: experiência de desenvolvimento pessoal

Questões de arquitetura e refatoração

Agora tenho vários objetos de jogo: pessoas, quadras e pisos. Mas para que os wireframes funcionem, cada entidade de objeto precisa ser informada se os próprios objetos estão em modo de demonstração ou se um quadro é simplesmente desenhado. Isto não é muito conveniente.

Pareceu-me que a arquitectura precisava de ser repensada de uma forma que revelasse algumas limitações:

  • Ter uma entidade que se renderiza e se atualiza é um problema porque essa entidade não será capaz de “saber” o que deve renderizar - uma imagem e um wireframe;
  • falta de uma ferramenta para troca de propriedades e comportamento entre entidades individuais (por exemplo, a propriedade is_build_mode ou renderização de comportamento). Seria possível usar herança, embora não exista uma maneira adequada de implementá-la no Rust. O que eu realmente precisava era do layout;
  • era necessária uma ferramenta de interação entre entidades para designar pessoas aos tribunais;
  • as próprias entidades eram uma mistura de dados e lógica que rapidamente saiu do controle.

Pesquisei mais e descobri a arquitetura ECS - Sistema de Componentes de Entidade, que é comumente usado em jogos. Aqui estão os benefícios do ECS:

  • os dados são separados da lógica;
  • composição em vez de herança;
  • arquitetura centrada em dados.

O ECS é caracterizado por três conceitos básicos:

  • entidades - o tipo de objeto ao qual o identificador se refere (pode ser um jogador, uma bola ou qualquer outra coisa);
  • componentes - as entidades são compostas por eles. Exemplo - componente de renderização, locais e outros. Estes são armazéns de dados;
  • sistemas - eles usam objetos e componentes, além de conterem comportamento e lógica baseados nesses dados. Um exemplo é um sistema de renderização que itera por todas as entidades com componentes de renderização e faz a renderização.

Depois de estudá-lo, ficou claro que o ECS resolve os seguintes problemas:

  • usar layout em vez de herança para organizar entidades sistemicamente;
  • livrar-se da confusão de códigos através de sistemas de controle;
  • usando métodos como is_build_mode para manter a lógica do wireframe no mesmo lugar - no sistema de renderização.

Foi o que aconteceu após a implementação do ECS.

recursos -> é aqui que estão todos os ativos (imagens)
src
- componentes
—posição.rs
- pessoa.rs
— tênis_court.rs
- andar.rs
- wireframe.rs
-mouse_tracked.rs
- recursos
—mouse.rs
- sistemas
- renderização.rs
- constantes.rs
-utils.rs
— world_factory.rs -> funções de fábrica mundial
— main.rs -> loop principal

Designamos pessoas para os tribunais

O ECS tornou a vida mais fácil. Agora eu tinha uma maneira sistemática de adicionar dados a entidades e adicionar lógica com base nesses dados. E isso, por sua vez, possibilitou organizar a distribuição das pessoas entre os tribunais.

O que eu fiz:

  • dados adicionados sobre tribunais atribuídos à Pessoa;
  • dados adicionados sobre pessoas distribuídas ao TennisCourt;
  • adicionado CourtChoosingSystem, que permite analisar pessoas e quadras, detectar quadras disponíveis e distribuir jogadores para elas;
  • adicionou um PersonMovementSystem, que procura pessoas designadas para os tribunais e, se elas não estiverem lá, as envia para onde elas precisam estar.

Jogando Rust em 24 horas: experiência de desenvolvimento pessoal

Resumindo

Eu realmente gostei de trabalhar neste jogo simples. Além disso, estou feliz por ter usado Rust para escrevê-lo, porque:

  • A ferrugem oferece o que você precisa;
  • possui excelente documentação, Rust é bastante elegante;
  • a consistência é legal;
  • você não precisa recorrer à clonagem, cópia ou outras ações semelhantes, o que eu fazia frequentemente em C++;
  • As opções são muito fáceis de usar e lidam muito bem com erros;
  • se o projeto pôde ser compilado, então 99% das vezes ele funciona e exatamente como deveria. Acho que as mensagens de erro do compilador são as melhores que já vi.

O desenvolvimento de jogos em Rust está apenas começando. Mas já existe uma comunidade estável e bastante grande trabalhando para abrir o Rust para todos. Por isso, olho para o futuro da língua com otimismo, aguardando com expectativa os resultados do nosso trabalho comum.

A Skillbox recomenda:

Fonte: habr.com

Adicionar um comentário