

Esse é um dos projetos que eu apresendo no meu curso Rusting with Style, para você entender como utilizar Pseudo Orientação a Objetos com traits e structs.
Acesse o REPO DO PROJETO e estude o código.
Nesse exemplo eu usei o ggez um framework simples para games 2D em Rust, semelhante ao projeto do Gopher Hunter.
O código apresentado é um template de um jogo desenvolvido em Rust, utilizando a biblioteca ggez para renderização e eventos. A seguir, detalharei como o design do game é estruturado, especialmente no que diz respeito ao uso de traits, structs e coleções polimórficas, bem como o que cada parte do código faz.
O jogo é baseado em um loop de eventos gerenciado pelo ggez::event::run. Ele renderiza um cenário (background fixo e elementos de cenário que se movem da direita para a esquerda) e um personagem principal (player), além de NPCs (personagens não-jogadores) que aparecem periodicamente. O player pode pular e recuar, e se colidir com um NPC, o jogo termina. Uma vez terminado, o jogador pode reiniciar ou encerrar.
Uma parte crucial do design do código é a utilização do trait GameObject. O trait funciona como uma interface que determina um conjunto de métodos obrigatórios para objetos do jogo: update, colidiu, desenhar e obter_propriedades. Isso permite o polimorfismo, ou seja, que diferentes tipos de personagens (Player, Cobra, Gopher, Xicara, etc.) possam ser armazenados em uma mesma coleção, contanto que eles implementem o trait.
Por que isso é útil?
Em linguagens com tipagem estática, coleções polimórficas são um desafio. Ao ter um trait, podemos criar um Vec<Box<dyn GameObject>> que armazena diferentes tipos de NPCs e iterar sobre eles de forma genérica, chamando métodos comuns.
GameObjecttrait GameObject {
fn update(&mut self, dt: std::time::Duration);
fn colidiu(&self, outro: &PropriedadesComuns) -> bool;
fn desenhar(&self, canvas: &mut graphics::Canvas);
fn obter_propriedades(&mut self) -> &mut PropriedadesComuns;
}
Ele define o que todo objeto do jogo precisa fazer:
PropriedadesComunsA struct PropriedadesComuns contém atributos que muitos objetos compartilham: imagens, posição, largura, altura, estado de pulo, estado invertido da imagem, etc. Isso permite que seja facilmente reutilizada por diferentes tipos de objetos do jogo (jogador, NPCs) sem duplicar código.
#[derive(Clone)]
struct PropriedadesComuns {
imagem1: graphics::Image,
imagem2: graphics::Image,
posicao: Vec2,
largura: f32,
altura: f32,
segundos_para_virar: f32,
velocidade: f32,
invertido: bool,
saiu_de_cena: bool,
pulando: bool,
caindo: bool,
limite_pulo: f32,
posicao_vertical_original: f32,
recuando: bool,
acelerando: bool,
}
A ideia: Ter um “miolo” com atributos e comportamentos recorrentes, que é utilizado tanto pelo player quanto pelos NPCs. A lógica padrão (como mover para a esquerda, alternar imagens, checar colisão) já é implementada no impl GameObject for PropriedadesComuns. Assim, objetos mais simples (como a Cobra ou Xicara) apenas utilizam essa implementação padrão.
PropriedadesComuns. Assim, Ferris implementa GameObject manualmente e chama seus próprios métodos de movimentação dentro do update.PropriedadesComuns, com pequenas diferenças. Por exemplo, o Gopher modifica o método update para permitir pulos aleatórios. A Xicara ajusta a posição inicial e velocidade de forma aleatória. Esses objetos se diferenciam do Ferris principalmente porque o player é controlado pelo usuário, enquanto NPCs seguem comportamentos automatizados.No código, há uma coleção Vec<Box<dyn GameObject>> chamada npcs. Isso significa que o vetor pode conter Cobra, Xicara, Gopher ao mesmo tempo, pois todos implementam o trait GameObject. Assim, no loop for npc in &mut self.npcs { ... }, podemos chamá-los de forma genérica, executando npc.update, npc.desenhar, etc., sem precisar saber o tipo específico do NPC.
Jogo e o Loop Principalstruct Jogo {
background: graphics::Image,
imagens_cenario: Vec<graphics::Image>,
dt: std::time::Duration,
cenarios: Vec<Cenario>,
player: Ferris,
npcs: Vec<Box<dyn GameObject>>,
...
}
O Jogo armazena:
Cenario, que não são polimórficos pois seu comportamento é simples. O Cenario não implementa GameObject, mas poderia ser reorganizado se quisessem tratá-lo de forma polimórfica também.Ferris.Box<dyn GameObject>.No update do Jogo, cada elemento é atualizado:
No draw do Jogo, desenha-se o background, cenários ativos, player e NPCs.
O EventHandler é implementado para Jogo, e nele há métodos como key_down_event para lidar com entradas de teclado, permitindo ao player pular ou recuar. Também há a possibilidade de reiniciar o jogo ou encerrá-lo.
GameObject): Definem o comportamento necessário para objetos do jogo.PropriedadesComuns): Contêm atributos compartilhados, evitando duplicação de código.GameObject, permitindo comportamento variado sob uma interface comum.Vec<Box<dyn GameObject>>): Permitem armazenar diferentes tipos de objetos em uma única coleção, simplificando o loop de atualização e desenho.Jogo, que orquestra atualização e desenho.O código utiliza traits para padronizar o comportamento de diferentes tipos de objetos, tornando o game design mais modular, extensível e organizado, enquanto as structs de propriedades comuns e o polimorfismo via Box<dyn GameObject> facilitam a criação de diversos elementos de jogo com comportamentos variados, mas interface uniforme.