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

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

Neste artigo, compartilharei minha experiência no desenvolvimento de um pequeno jogo em Rust. A criação da versão funcional levou cerca de 24 horas (principalmente trabalhei à noite ou nos finais de semana). O jogo ainda está longe de ser concluído, mas acredito que a experiência será útil. Vou contar o que aprendi e algumas observações feitas ao construir o jogo do zero.

A Skillbox recomenda: Curso Prático de Dois Anos «Sou um desenvolvedor web PRO».

Lembramos: para todos os leitores do «Habr» — desconto de 10.000 rublos na inscrição em qualquer curso Skillbox com o código promocional «Habr».

Por que Rust?

Escolhi esta linguagem porque ouvi muitas coisas boas sobre ela e vejo que está se tornando cada vez mais popular na área de desenvolvimento de jogos. Antes de escrever o jogo, tive uma experiência leve no desenvolvimento de aplicativos simples em Rust. Isso foi suficiente para sentir uma certa liberdade ao escrever o jogo.

Por que um jogo e que tipo de jogo?

Criar jogos é divertido! Eu gostaria que houvesse mais motivos, mas para projetos 'caseiros' eu escolho temas que não estão muito intimamente relacionados ao meu trabalho habitual. Que tipo de jogo? Queria fazer algo como um simulador de tênis, onde se combinam Cities Skylines, Zoo Tycoon, Prison Architect e, naturalmente, tênis. Em suma, o resultado foi um jogo sobre uma academia de tênis, onde as pessoas vêm para jogar.

Preparação técnica

Eu queria usar Rust, mas não sabia exatamente quão 'do zero' precisaria começar a trabalhar. Não queria escrever shaders pixelados e usar drag-n-drop, então procurei as soluções mais flexíveis.

Encontrei recursos úteis, que compartilho com vocês:

Estudei vários motores de jogo em Rust, e no final escolhi o Piston e o ggez. Já tinha encontrado esses durante um projeto anterior. Acabei escolhendo o ggez, pois pareceu mais adequado para a realização de um pequeno jogo 2D. A estrutura modular do Piston é excessivamente complexa para um desenvolvedor iniciante (ou para alguém que está trabalhando com Rust pela primeira vez).

Estrutura do jogo

Gastei um tempo pensando sobre a arquitetura do projeto. O primeiro passo é criar a 'terra', as pessoas e as quadras de tênis. As pessoas devem se mover pelas quadras e esperar. Os jogadores devem ter habilidades que melhoram com o tempo. Além disso, deve haver um editor que permita adicionar novas pessoas e quadras, mas isso já não é gratuito.

Após pensar em tudo, comecei a trabalhar.

Criação de jogos

Início: círculos e abstrações

Eu peguei um exemplo do ggez e consegui um círculo na tela. Impressionante! Agora, vamos falar sobre algumas abstrações. Achei interessante abstrair da ideia de um objeto de jogo. Cada objeto deve ser renderizado e atualizado, como 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 permitiu obter uma ótima lista de objetos que posso atualizar e renderizar em um ciclo igualmente ótimo.

mpl event::EventHandler para MainState {
    fn update(&mut self, context: &mut Context) -> GameResult {
        // Atualizar todos os objetos
        for object in self.objects.iter_mut() {
            object.update(context)?;
        }
 
        Ok(())
    }
 
    fn draw(&mut self, context: &mut Context) -> GameResult {
        graphics::clear(context);
 
        // Desenhar todos os objetos
        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. Eu passei um tempo separando os arquivos e otimizando a estrutura de diretórios. Aqui está como tudo ficou depois disso:
resources -> é aqui que estão todos os ativos (imagens)
src
— entidades
— game_object.rs
— circle.rs
— main.rs -> loop principal

Pessoas, quadras e imagens

A próxima etapa é criar o objeto de jogo Person e carregar as imagens. Tudo deve ser construído com base em blocos de 32x32.

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

Quadras de tênis

Depois de estudar como são as quadras de tênis, decidi fazer as quadras usando blocos de 4x2. Inicialmente, poderia criar uma imagem desse tamanho ou juntar 8 blocos individuais. Mas depois percebi que só eram necessárias duas peças únicas, e aqui está o porquê.

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

Cada seção da quadra é composta pela peça 1 ou pela peça 2. Elas podem estar dispostas normalmente ou invertidas em 180 graus.

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

Modo principal de construção (montagem)

Depois de conseguir renderizar as quadras, as pessoas e os mapas, percebi que também precisava de um modo básico de montagem. Isso foi implementado da seguinte forma: quando o botão é pressionado — o objeto é selecionado, e o clique o coloca no lugar desejado. Assim, o botão 1 permite selecionar a quadra, e o botão 2 permite selecionar o jogador.

Mas é necessário lembrar o que 1 e 2 significam, então eu adicionei um wireframe para que fique claro qual objeto foi selecionado. Aqui está como isso aparece.

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

Questões sobre arquitetura e refatoração

Agora eu tenho vários objetos de jogo: pessoas, quadras e andares. Mas para que os wireframes funcionem, é necessário informar cada entidade do objeto se os próprios objetos estão no modo de demonstração ou se apenas um contorno está desenhado. Isso não é muito prático.

Eu percebi que era necessário repensar a arquitetura para que algumas limitações surgissem:

  • a presença de uma entidade que se representa e se atualiza sozinha é um problema, pois essa entidade não poderá 'saber' o que deve renderizar - a imagem e o wireframe;
  • a falta de uma ferramenta para compartilhar propriedades e comportamentos entre entidades específicas (por exemplo, a propriedade is_build_mode ou o desenho de comportamentos). Poderíamos usar herança, embora em Rust não exista uma maneira adequada de implementá-la. O que eu realmente precisava era da composição;
  • uma ferramenta para interagir entre as entidades era necessária para designar pessoas nas quadras;
  • as próprias entidades eram uma mistura de dados e lógica, que rapidamente saía de controle.

Eu fiz um estudo adicional e descobri a arquitetura ECS — Entity Component System, que é comumente utilizada em jogos. Aqui estão as vantagens do ECS:

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

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

  • entidades — um tipo de objeto que é referenciado por um identificador (isso pode ser um jogador, uma bola ou algo assim);
  • componentes — com os quais as entidades são compostas. Um exemplo é um componente de renderização, posição e outros. Esses são armazenamentos de dados;
  • sistemas — eles usam tanto objetos quanto componentes, além de conter comportamentos e lógica que se baseiam nesses dados. Um exemplo é um sistema de renderização que percorre todas as entidades com componentes de renderização e realiza o desenho.

Após o estudo, ficou claro que o ECS resolve os seguintes problemas:

  • uso de composição em vez de herança para a organização sistêmica das entidades;
  • eliminação da confusão do código por meio de sistemas de controle;
  • uso de métodos como is_build_mode para manter a lógica do wireframe em um só lugar — no sistema de renderização.

Aqui está o que resultou após a implementação do ECS.

resources -> é aqui que estão todos os ativos (imagens)
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 -> funções da fábrica mundial
— main.rs -> loop principal

Atribuímos pessoas às quadras

A ECS facilitou a vida. Agora eu tinha um caminho sistemático para adicionar dados às entidades e incorporar lógica baseada nesses dados. Isso, por sua vez, permitiu organizar a distribuição de pessoas nas quadras.

O que eu fiz:

  • adicionei dados sobre as quadras designadas à Pessoa;
  • adicionei dados sobre as pessoas distribuídas no TennisCourt;
  • adicionei o CourtChoosingSystem, que permite analisar pessoas e locais, descobrir quadras disponíveis e distribuir jogadores nelas;
  • adicionei o sistema PersonMovementSystem, que procura pessoas designadas para as quadras e, se elas não estiverem lá, envia as pessoas para onde precisam.

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

Resumindo

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

  • Rust lhe dá o que você precisa;
  • ele tem uma excelente documentação, Rust é bastante elegante;
  • a consistência é incrível;
  • não preciso recorrer a clonagem, cópia ou ações semelhantes, que eu frequentemente fazia em C++;
  • Options são muito convenientes para trabalhar, e elas também lidam bem com erros;
  • se o projeto foi compilado com sucesso, 99% das vezes ele funciona exatamente como deveria. As mensagens de erro do compilador, na minha opinião, 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 tornar Rust acessível a todos. Portanto, vejo o futuro da linguagem com otimismo, aguardando ansiosamente os resultados do nosso esforço coletivo.

A Skillbox recomenda:

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster