Como eu criei o Recycle! VR



No artigo anterior, tentamos criar uma cena básica no A-Frame para experimentar os conceitos básicos da estrutura na prática. Neste artigo, gostaria de compartilhar minha experiência na criação de um jogo no A-Frame - Recycle! VR O repositório do projeto está disponível no seguinte link .

Reciclagem!?


A ideia de criar um jogo surgiu quase imediatamente assim que descobri o Web VR. Embora, em geral, acredito que os jogos na Web, em qualquer caso, sejam inferiores a bons projetos, mesmo para dispositivos móveis, sem mencionar computadores pessoais e consoles. Mas, parece-me, o jogo é o teste mais difícil. Comecei a pensar exatamente no que posso fazer. Eu assisti a outros projetos e fiquei imediatamente impressionado com a oportunidade de pegar algo usando o controlador. E desde que eu estou associado a uma organização que está envolvida na coleta de lixo separada há algum tempo, a resposta veio por si só. Reciclagem. Nós pegamos o lixo e jogamos no lixo. O que poderia ser mais fácil? Mas tudo acabou não sendo tão simples, e isso, de fato, será discutido mais adiante.

Seleção de framework


No início do trabalho no jogo, eu conhecia apenas duas estruturas mais ou menos sérias: React 360, A-Frame. Obviamente, o A-Frame era mais adequado para criar o jogo. Sim, agora eu sei que ainda existe um mecanismo de jogo PlayCanvas que também suporta VR, mas é tarde demais. Além disso, descobriu-se que o A-Frame também não é ruim para a criação de jogos.

Por onde começar?


Comecei estudando exemplos oficiais de jogos de desenvolvedores do A-Frame. O benefício destes não é suficiente. A-Blast, A-Painter, Museum, Super Craft e agora também Gunters of Oasis. De todos os projetos apresentados, gostei mais do A-Blast - um jogo de tiro em que você precisa lutar com as criaturas mais fofas do universo. Portanto, eu queria levar este jogo como modelo para o meu. Mas não deu certo. E a razão para isso foi a estrutura do jogo. Pareceu-me que ela estava muito bagunçada e sem pensar. Talvez não seja necessário mais, mas eu queria fazer algo mais conveniente e fácil de entender.

Estrutura


A estrutura do A-Blast representa apenas um ponto de entrada - o arquivo index.html, que contém uma cena com todos os ativos, entidades básicas do jogo, controles e tudo em geral.



Como você pode ver na captura de tela, além dos componentes e sistemas necessários (o A-Frame usa o padrão Entity Component System), também existem marcadores e inimigos - essencialmente os mesmos sistemas, mas por algum motivo têm seu próprio invólucro. Geralmente diga que esse código não é fácil de entender. Então, decidi pensar em como esse código pode ser estruturado. A primeira idéia é dividir a cena em suas partes constituintes. Por que um roteador e modelos seriam úteis, o que renderizaria essa ou aquela parte da cena. Depois de pesquisar o primeiro e o segundo (não, não cinco minutos), não encontrei nada. Embora eu seja um defensor da regra, não escreva bicicletas, mas desta vez eu tive que escrever minha decisão. Embora, em algum lugar de duas a três semanas eu me deparei com modelos de Kevin Ngo. Mas já era tarde demais.

Roteador e padrões




E assim, um modelo de roteador de quadro entra em cena. O que ele pode fazer? Como mencionado acima, sua principal tarefa é renderizar as partes necessárias do jogo, por exemplo, a tela de título, o campo de jogo, a tela final do jogo, etc. Como fazer isso? Em princípio, você pode encontrar tudo o que precisa na documentação do módulo no github, mas, em resumo, temos o seguinte:

<a-scene router> ... <!-- Routes --> <a-route id="start-screen" template="start-screen"></a-route> <a-route id="game-field" template="game-field"></a-route> <a-route id="game-over" template="game-over"></a-route> <a-route id="how-to-play" template="how-to-play"></a-route> <!-- End Routes --> ... <!-- Templates --> <a-template name="controls"></a-template> <!-- End Templates --> ... </a-scene> 

  1. Estamos adicionando o componente do roteador à cena.
  2. Adicione uma rota para cada parte do aplicativo (quadros da cena). Uma rota para a tela inicial, outra para o campo de jogo, etc.
  3. Renderize modelos diretamente através de modelos a
  4. Se necessário, mudamos de rota

     this.el.systems.router.changeRoute('game-field'); 

    Nota : este exemplo se refere ao código da cena, para que possamos chamar o sistema do roteador diretamente.
  5. Definimos e conectamos os modelos, algo como isto:

     AFRAME.registerTemplate('game-field', ` <a-sub-assets> <a-asset-item id="glass" src="/assets/models/glass_bottle.gltf"></a-asset-item> ... <audio id="fail" src="/assets/sounds/fail.wav" preload></audio> </a-sub-assets> <a-template name="button" options="text: EXIT; position: 0 1 4; rotation: 0 180 0; event: stop-game"></a-template> <a-entity id="indicator" indicator visible="false" position="0 1 -2" text="align: center; width: 4; color: #00A105; value: -1" ></a-entity> <a-entity game-field-manager></a-entity> `); 

    Nota: a-sub-assets permite carregar ativos e ativos, mas apenas com a diferença de que há uma verificação por padrão e se o ativo já tiver sido adicionado, ele não será adicionado novamente quando a rota for alterada.

    Nota 2: Normalmente você pode usar modelos apenas com a sequência de modelos ES6. Caso contrário, ele pode se transformar em "string" + var + "string", não é legal. Kevin, por exemplo, tem suporte para mecanismos de modelo. Mas por que complicar, certo?

Assim, você pode criar uma estrutura de aplicativo conveniente que conterá o seguinte: componentes, sistemas, modelos, estados, bibliotecas . Nada mais e tudo está nas prateleiras.

Manipulando objetos




A primeira tarefa a ser resolvida foi a manipulação de objetos. Eu precisava de um funcional como agarrar - arremesso. Inicialmente, comecei a refletir sobre como criar esse componente do zero. Puramente no nível filistino, tal reflexão é permitida: temos um controlador (no caso de uma área de trabalho, é um cursor), tem uma posição. Também temos certos objetos, por exemplo cubos, eles também têm uma posição. Alterando a posição do controlador, devemos alterar a posição do objeto. É simples? Então, na verdade sim, mas não vai funcionar. Mencionarei alguns pontos de uma lista muito longa para convencê-lo disso:

  • O cursor no A-Frame é um descendente de uma câmera e possui coordenadas relativas;
  • A posição do controlador não é suficiente, você ainda precisa considerar a orientação, a distância do objeto, a posição da câmera (player);
  • Para objetos com um corpo físico, isso não funcionará, porque as coordenadas da geometria estão conectadas às coordenadas do corpo.

É bom que o bom Sr. Wil Murphy e seus amigos tenham superado as mãos . Essencialmente, esta biblioteca contém todos os componentes necessários:

  • pairável . Orientação. Aponte o controlador ou cursor sobre a zona de colisão do objeto (geralmente o objeto inteiro)
  • agarrável : captura. Pegue um objeto usando o botão apropriado e arraste-o
  • esticável : segure com as duas mãos e estique \ aperte
  • draggable \ dropable : essencial para determinar o evento "o item foi lançado em um local específico"

Você pode encontrar tudo o que precisa sobre como configurar e conectar super mãos no repositório mencionado acima. Eu só quero chamar a atenção para várias nuances:

  • Crie mixins separados para a mão direita e esquerda. Separe os componentes por tipo de dispositivos suportados. Por exemplo, a mão direita, além de oculus-touch, vive-controls, windows-motion-controls, também pode haver oculus-go-controls e gear-vr-controls. A mão esquerda precisa estar oculta para os capacetes móveis da BP. Cada controlador deve conter mãos de mixagem e um componente de super-mãos. Um exemplo ;
  • Se você especificou objetos: .clsname para reycaster, não se esqueça de adicioná-lo a cada elemento que pode ser obtido usando o controlador, caso contrário, nenhum evento para super mãos falhará. Claro, se colliderEvent: raycaster-intersection ;
  • Arrastar com o mouse projeta as coordenadas 2D no mundo 3d, portanto é melhor usar o cursor para a área de trabalho.

Adicionar física


Adicionar física a um quadro é realmente muito simples. Existe um sistema especial para isso. É adicionado à cena e pronto, a física já está no seu bolso.

 <a-scene physics="debug: false"> <a-box dynamic-body position="0 1 -2"></a-box> <a-box id="floor" static-body></a-box> </a-scene> 

Mark : debug: true permite a visualização de corpos físicos vinculados à geometria. É conveniente quando você precisa "delinear" um objeto.

Na verdade, este é um invólucro para cannon.js que faz todo o trabalho sujo de comparar geometria e corpos físicos para você. Novamente, sobre como esse sistema funciona, você pode encontrá-lo na descrição do repositório. E gostaria de me concentrar em apenas um ponto importante para o meu jogo.

Eu precisava ter certeza de que, ao pressionar o botão no lixo, uma certa força era definida (quanto mais você mantiver o botão pressionado, maior será a força). Como se viu, essa tarefa não é tão simples quanto parece à primeira vista. Bem, o que é tão complicado? - você diz, nós aplicamosImpluse e voila. Na verdade não ... Ele define a rotação de um objeto ao longo de um vetor aplicado ao centro do corpo. Usando esse método, podemos apenas emular um yule. Embora, se você definir um vetor com o ângulo correto para o plano, poderá obter algo semelhante a um empurrão. Mas não era disso que eu precisava.

Como se viu, eu precisava de velocidade ao definir esse parâmetro, o objeto inicia seu movimento em uma determinada direção. Essa direção é especificada pelo vetor. E aqui começa a diversão. Como encontrar esse vetor? Encontrei duas opções:

  1. Obtenha o quaternion do controlador (ou câmera para a área de trabalho), que descreve sua orientação no espaço. Crie um vetor V1 = <1,1,1>, multiplique pela força de arremesso e aplique a orientação a tudo isso.

     const velocityVector = new THREE.Vector3(1,1,1); velocityVector.multiplyScalar(this.force); velocityVector.applyQuaternion(controllerQuaternion); this.grabbed.body.velocity.set(velocityVector.x, velocityVector.y, velocityVector.z); 
  2. Encontre a posição do controlador (cursor) e a posição do objeto que está sendo lançado. Calcule o vetor de direção para dois pontos. Normalize o vetor. E multiplique pela força.

     const directionX = (trashPosition.x - zeroPosition.x); const directionZ = (trashPosition.z - zeroPosition.z); const vectorsLength = Math.sqrt(Math.pow(directionX, 2) + Math.pow(directionZ, 2)); const x = (directionX / vectorsLength) * this.force; const y = this.force; const z = (directionZ / vectorsLength) * this.force; this.grabbed.body.velocity.set(x , y, z ); 

Eu escolhi a segunda opção porque nela posso contar apenas x e z. E prepare-se, já que eu precisava jogar ao longo do arco para que o lixo despejado caísse na cesta, apesar do usuário segurando o controlador.

Algumas palavras sobre o modelo




Desde o início, eu decidi fazer um jogo no estilo low-poly . Embora o WebGL seja capaz de renderizar cenas relativamente complexas hoje em dia, seu desempenho ainda é inferior às bibliotecas avançadas, como DirectX, Vulkan, Mantle, etc. Tudo depende também do desempenho do dispositivo do usuário. Como gostaria de me concentrar em capacetes móveis da BP mais acessíveis (Oculus Go, Gear VR), acho que o baixo poli é uma das poucas soluções para criar um aplicativo ou jogo de VR. Embora, é claro, tudo dependa do volume.

Ok, low-poly é tão low-poly, mas como fazer tudo isso? Tudo é muito simples, existe uma boa ferramenta de código aberto - Blender . Acredite, ele é capaz de muito, mas para tarefas simples ele não é muito adequado. Existem muitos materiais de treinamento relacionados à modelagem no Blender e não será difícil encontrá-los. Eu só queria focar sua atenção em vários pontos relacionados ao desenvolvimento web:

  1. O exportador de três js está desatualizado. Precisa encontrar e fornecer exportador de GLTF . GLTF é um formato especial projetado para a web. E sim, isso é JSON.
  2. O GLTF não suporta o Cycles Renderer, portanto você deve usar o Blender Renderer. E isso significa que não haverá nós legais, transformações de cores, realces de metal (podem ser feitos de maneira diferente).
  3. Você precisa exportar apenas o item selecionado. Você não precisa de câmeras e luzes extras? Arquivo> Exportar> gltf 2.0. No menu esquerdo, Exportar GLTF 2.0> Exportar somente selecionado.
  4. Começamos a exportar da posição <0, 0, 0> no Blender. É melhor dimensionar no mesmo local, para que mais tarde você não use o componente de escala em um quadro.
  5. Se você desenhar um espaço aberto como em Reciclar! VR, você precisa adicionar objetos apenas para onde o jogador possa olhar teoricamente. Atrás, atrás das casas, na Reciclagem! existem algumas árvores e somente no local em que o usuário pode vê-las. Não é necessário sobrecarregar a cena.
  6. Se você precisar alterar o material do modelo, precisará esperar até ele carregar, obter o próprio modelo, retirar todos os nós (o GLTF contém informações não apenas sobre malhas)

     e.detail.model.traverse((node) => { if (node.isMesh) { node.material.color = new THREE.Color(someColor); } }); 

Em conclusão


Obrigado a todos pela atenção! Lembro novamente que o repositório do projeto está disponível no seguinte link . Quem quiser trazer algo novo para este jogo - seja bem-vindo.

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


All Articles