Jogo de ferrugem 24 horas: experiência em desenvolvimento pessoal

imagem

Neste artigo, falarei sobre minha experiência pessoal no desenvolvimento de um pequeno jogo no Rust. Demorou 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á útil. Vou contar o que aprendi e algumas das observações feitas ao criar o jogo do zero.

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

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

Por que ferrugem?


Eu escolhi esse idioma porque ouvi muitas coisas boas sobre ele e vejo que ele está se tornando cada vez mais popular no campo do desenvolvimento de jogos. Antes de escrever o jogo, eu tinha pouca experiência no desenvolvimento de aplicativos simples no Rust. Isso foi o suficiente para sentir uma certa liberdade enquanto escrevia o jogo.

Por que exatamente o jogo e que tipo de jogo?


Fazer jogos é divertido! Gostaria por mais razões, mas para projetos de “casa” escolho tópicos que não estão muito relacionados ao meu trabalho habitual. Que tipo de jogo? Eu queria criar algo como um simulador de tênis, que combina Cities Skylines, Zoo Tycoon, Prison Architect e o próprio tênis. Em geral, acabou 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 como "do zero" eu precisaria começar. Como não queria escrever pixel shaders e usar o recurso de arrastar e soltar, estava procurando as soluções mais flexíveis.

Encontrei recursos úteis que eu compartilho com você:


Eu explorei vários mecanismos de jogo Rust, eventualmente escolhendo Piston e ggez. Me deparei com eles ao trabalhar em um projeto anterior. No final, escolhi o ggez, porque parecia mais adequado para a implementação de um pequeno jogo 2D. A estrutura modular do Piston é muito complexa para um desenvolvedor iniciante (ou alguém que trabalhe com o Rust pela primeira vez).

Estrutura do jogo


Passei um pouco de tempo pensando na arquitetura do projeto. O primeiro passo é fazer a "terra", pessoas e quadras de tênis. As pessoas devem se movimentar pelos tribunais 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 tribunais, mas isso não é mais gratuito.

Pensando em tudo, comecei a trabalhar.

Criação de jogos


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

Peguei um exemplo de ggez e fiz um círculo na tela. Incrível Agora algumas abstrações. Pareceu-me que é bom ignorar a ideia de um 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 pedaço de código me permitiu obter uma excelente lista de objetos que eu posso atualizar e renderizar em um loop igualmente excelente.

 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 pouco de tempo para separar os arquivos e otimizar a estrutura de diretórios. Aqui está como tudo começou a cuidar disso:

recursos -> é aqui que estão todos os ativos (imagens)
src
- entidades
- game_object.rs
- circle.rs
- main.rs -> loop principal

Pessoas, pisos e imagens

O próximo passo é criar um objeto de jogo Person e carregar imagens. Tudo deve ser baseado em azulejos de tamanho 32 * 32.



Quadras de tênis

Tendo estudado a aparência das quadras de tênis, decidi fazê-las com 4 * 2 peças. Inicialmente, era possível criar uma imagem desse tamanho ou montar 8 peças separadas. Mas então eu percebi que apenas duas peças únicas são necessárias, e é por isso.

No total, temos dois desses ladrilhos: 1 e 2.

Cada seção da quadra consiste no ladrilho 1 ou no ladrilho 2. Eles podem ser organizados como de costume ou ser virados de cabeça para baixo em 180 graus.



O principal modo de construção (montagem)

Depois que consegui obter a renderização de sites, pessoas e mapas, percebi que também era necessário um modo básico de compilação. Foi implementado assim: quando o botão é pressionado, o objeto é selecionado e o clique o coloca no lugar certo. Portanto, o botão 1 permite selecionar uma quadra e o botão 2 permite selecionar um jogador.

Mas você ainda precisa se lembrar do que queremos dizer 1 e 2, então adicionei um wireframe para deixar claro qual objeto está selecionado. Aqui está como fica.


Perguntas sobre arquitetura e refatoração

Agora eu tenho vários objetos de jogo: pessoas, quadras e pisos. Mas, para que os wireframes funcionem, você precisa informar a cada entidade do objeto se os objetos estão no modo de demonstração ou se um quadro é simplesmente desenhado. Isso não é muito conveniente.

Pareceu-me que eu precisava repensar a arquitetura para que algumas limitações fossem reveladas:

  • a presença de uma entidade que se exibe e se atualiza é um problema, porque essa entidade não poderá "saber" o que deve renderizar - uma imagem e uma estrutura de arame;
  • falta de uma ferramenta para trocar propriedades e comportamento entre entidades individuais (por exemplo, a propriedade is_build_mode ou o comportamento de renderização). A herança poderia ser usada, embora não exista uma maneira normal de implementá-la no Rust. O que eu realmente precisava era do layout;
  • era necessária uma ferramenta para a interação de entidades entre si para designar pessoas para os tribunais;
  • as próprias entidades eram uma mistura de dados e lógica, que rapidamente saíram do controle.

Fiz algumas pesquisas e descobri a arquitetura do ECS - Entity Component System , que é comumente usada em jogos. Aqui estão os benefícios do ECS:

  • os dados são separados da lógica;
  • layout em vez de herança;
  • arquitetura orientada a 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 outra coisa);
  • componentes - entidades consistem deles. Um exemplo é um componente de renderização, layout e outros. É um armazém 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 sobre todas as entidades com componentes para renderização e está envolvido na renderização.

Depois de estudar, ficou claro que o ECS resolve esses problemas:

  • usar layout em vez de herança para organização do sistema de entidades;
  • livrar-se de um hash de código devido a sistemas de controle;
  • usando métodos como is_build_mode para armazenar a lógica do wireframe no mesmo local - no sistema de renderização.

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

recursos -> é aqui que estão todos os ativos (imagens)
src
- componentes
- position.rs
- person.rs
- tennis_court.rs
- floor.rs
- wireframe.rs
- mouse_tracked.rs
- recursos
- mouse.rs
- sistemas
- rendering.rs
- constants.rs
- utils.rs
- world_factory.rs -> funções da fábrica mundial
- main.rs -> loop principal

Designar 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, tornou possível organizar a distribuição de pessoas por tribunal.

O que eu fiz:

  • dados adicionados sobre os tribunais designados à Pessoa;
  • dados adicionados sobre pessoas distribuídas ao TennisCourt;
  • adicionado CourtChoosingSystem, que permite analisar pessoas e sites, encontrar tribunais disponíveis e distribuir jogadores para eles;
  • adicionou o sistema PersonMovementSystem, que procura pessoas designadas para os tribunais e, se não estiverem lá, envia pessoas quando necessário.


Resumir


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

  • A ferrugem dá o que você precisa;
  • ele tem excelente documentação, Rust é muito elegante;
  • constância é legal;
  • Você não precisa recorrer à clonagem, cópia ou outras ações semelhantes, como costumava fazer em C ++;
  • As opções são muito convenientes para o trabalho, elas também lidam perfeitamente com os erros;
  • se o projeto puder ser compilado, em 99% ele funcionará e exatamente como deveria. As mensagens de erro do compilador, ao que me parece, são as melhores que eu já vi.

O desenvolvimento de jogos no Rust está apenas começando. Mas já existe uma comunidade estável e razoavelmente grande trabalhando para abrir o Rust para todos. Portanto, olho para o futuro da linguagem com otimismo, ansioso pelos resultados de nosso trabalho comum.

A Skillbox recomenda:


Source: https://habr.com/ru/post/pt444364/


All Articles