Batalha JS: como eu escrevi meu eval ()

Você pode se lembrar de Alexander Korotayev na versão do navegador de “Heróis do Poder e da Magia”: a descriptografia de seu relatório sobre ela reuniu uma enorme quantidade de pontos de vista sobre Habré. E agora ele criou um jogo focado em programadores: você precisa jogá-lo com um código JS.

Desta vez, o desenvolvimento não levou semanas, mas semanas, mas sem desafios interessantes de qualquer maneira que não poderia ser feito. Como tornar o jogo conveniente, mesmo para desenvolvedores que não tocaram no JavaScript? Como se proteger de maneiras simples de enganar um jogo?



Como resultado, Alexander novamente fez um relatório sobre o HolyJS e nós (os organizadores da conferência) preparamos novamente uma versão em texto para Habr.


Meu nome é Alexander Korotaev, trabalho na Tinkoff.ru, estou envolvido no front-end. Além disso, como parte da comunidade Spb-frontend, ajudo a organizar mitaps. Eu faço o podcast Drinkcast, convidamos pessoas interessantes e discutimos vários tópicos.

Qual é a essência do brinquedo? Primeiro, você precisa escolher uma unidade dentre as propostas, este é um sistema de RPG: cada unidade tem seus próprios pontos fortes e fracos. Você vê quais unidades o inimigo escolheu e se vingou dele. Então você precisa escrever em JavaScript um script para o comportamento do seu exército - em outras palavras, o script "o que todas as unidades devem fazer no campo de batalha".

Isso é feito no modo de depuração: na verdade, você debita o código e, em seguida, os dois oponentes pressionam seu código, e a batalha entre os dois lados começa.

Assim, você pode ver como dois scripts, duas lógicas e dois algoritmos se enfrentam. Eu sempre quis fazer algo assim, e agora está finalmente implementado.

Em ação, tudo se parece com isso:


E como foi o trabalho nele? Muito trabalho foi feito na documentação. Quando o jogador se senta no laptop, ele vê a documentação, na qual tudo é descrito com bastante detalhes.

Levei muito tempo para o seu layout, revisão e questionamento entre as pessoas, se é claro. Como resultado, ficou claro para sishnikov, javistas e outros desenvolvedores que não sabem nada sobre JS. Você pode até promover o JavaScript com este brinquedo: "Não é assustador, veja como você pode escrever nele, até algo divertido acontece".



Tivemos um grande torneio em nossa empresa, no qual praticamente todos os programadores de que participamos.

Da tecnologia, usei o mecanismo de jogo mais popular do mundo do JS - Phaser. O maior e mais usado Editor Ace. Este é um editor na Web, muito semelhante ao Sublime ou VSCode, que pode ser incorporado em uma página da Web. Também usei o RxJS para trabalhar com interações assíncronas de diferentes usuários e o Preact para renderizar html. Das tecnologias nativas, ele trabalhou especialmente com trabalhadores e websocket.





Jogos para programadores


O que são jogos para programadores em geral? Na minha opinião, são jogos em que você precisa codificar e, em seguida, obtém algum resultado engraçado que pode ser comparado a alguém, essa é uma batalha. Desses jogos online disponíveis, eu conheço "Elevator Saga" - você escreve scripts para elevadores de acordo com certos parâmetros. "Screeps" - sobre biologia, moléculas, escreva scripts para eles.

Também existem brinquedos que às vezes estão em conferências. O mais popular deles é o "Code in the Dark", também o apresentamos hoje. A propósito, “Code in the dark” me inspirou de várias maneiras.



Por que isso foi feito? Eu consegui a tarefa de que você precisasse de algo bacana com um estande na conferência, algo incomum. Não que houvesse eychars com questionários. É claro que todo mundo quer atrair atenção e coletar contatos. Decidimos ir além e criamos algo interessante e divertido para os programadores. Percebi que os programadores querem lutar, competir e precisamos dar a eles essa oportunidade. É necessário criar uma posição sobre a qual eles virão e codificarão.

Gamificação Fizemos isso não apenas entre os praticantes de programadores, mas também entre os estudantes. Realizamos essas partidas nos institutos no dia da carreira. Precisávamos de alguma forma ver que tipo de caras havia, adequado para nós ou não. Usamos a gamificação para atrair as pessoas para o processo, para ver como elas agem, o que fazem. Eles tocaram e se distraíram, mas isso nos deu informações. Alguns pressionaram o código sem executá-lo uma vez e ficou imediatamente óbvio que era muito cedo para o desenvolvimento.



Como parecia na primeira versão. Era uma tela principal e dois laptops para jogadores. Tudo isso contatou o servidor, o servidor armazenou o Estado e vasculhou-o entre todos os clientes conectados. Cada tela era um cliente conectado. Os laptops dos players eram telas interativas a partir das quais esse estado podia ser alterado. As telas são rigidamente conectadas a um servidor.

História de falta de tempo


A primeira história que me deparei neste desenvolvimento é a história que eu tinha muito pouco tempo. Literalmente em cinco minutos, surgiu uma idéia; em cinco segundos, um nome foi inventado quando foi necessário criar um repositório no GitHub. Eu poderia passar tudo apenas quatro horas da noite, tirando-as até da minha esposa. Como resultado, eu só tinha três semanas antes da conferência para perceber isso pelo menos de alguma maneira. Tudo começou para que fosse apenas necessário ter uma ideia no âmbito de um brainstorm, em cinco minutos a idéia nasceu: “Vamos escrever algum tipo de inteligência artificial para RPG em JS”. É legal, divertido, eu posso implementá-lo.



Na primeira implementação, a tela tinha um editor de código e uma tela de batalha, na qual a batalha estava em si. Phaser, Ace Editor e Node.js puro foram usados ​​como um servidor sem estruturas. Porém, o que me arrependi mais tarde, mas nada de especial foi exigido do servidor.



Consegui perceber então o Renderer, que pintou a própria batalha. A parte mais difícil foi a sandbox para o código JS, ou seja, uma sandbox na qual tudo deveria ser executado isoladamente para cada jogador. Também houve compartilhamento de estado que veio do servidor. Os jogadores de alguma forma mudaram de estado, jogaram no servidor, o servidor enviou o restante para os soquetes da web. O servidor era uma fonte de verdade e todos os clientes conectados confiavam no que vinha do servidor.

Sandbox


O que é tão difícil de implementar uma caixa de areia? O fato é que a sandbox é o mundo inteiro para o código no qual o código deve existir. Ou seja, você cria para ele o mesmo mundo que o mundo inteiro, mas consistindo apenas de algumas convenções, a partir de uma API com a qual você pode interagir. Como implementar isso em JS? Parece que o JS não é capaz disso, é tão cheio de buracos e gratuito que não dá para colocar completamente o código do usuário em uma caixa sem usar uma máquina virtual separada com um sistema operacional separado.



O que a caixa de areia deve fazer?

Primeiro, como eu disse, isole o código. Deve ser um mundo do qual não se possa sair.

Além disso, a API de gerenciamento de unidades deve ser lançada lá. Os jogadores devem interagir com o campo de batalha, movendo as unidades, direcionando-as, dando-lhes um vetor de ataque.
E quaisquer ações de unidades são assíncronas, ou seja, devem funcionar de alguma forma com código assíncrono.



O que eu queria dizer sobre assincronia? O fato é que no JS é basicamente implementado usando promessas. Tudo está claro para todos aqui, as promessas são ótimas, funcionam perfeitamente, quase sempre estivemos conosco. Por muitos anos, todo mundo sabe como trabalhar com eles, mas esse brinquedo não era apenas para javascripts. Imagine se eu começasse a explicar aos Javistas como escrever um código de batalha usando promessas? Como fazer então-então-então, por que às vezes não é necessário ... O que fazer com condições ou loops?



Obviamente, você pode seguir o melhor caminho e seguir a sintaxe assíncrona / aguardar. [Slide 8:57] Mas você também pode imaginar como programadores que não são javascript podem explicar que você precisa esperar antes de quase todas as linhas? Como resultado, a melhor maneira de trabalhar com assincronia é não usá-lo.



Crie o código mais síncrono e a API mais simples, semelhante a quase qualquer linguagem de programação. Nós fabricamos um brinquedo não apenas para pessoas que escrevem em JS, queremos torná-lo acessível a todos que sabem codificar.



Todos nós precisamos começar de alguma forma. O usuário escreve o código, e precisamos executá-lo, mova as unidades no mapa. A primeira coisa que vem à mente é que precisamos de eval () mais a instrução não recomendada com, que não é recomendada para uso no MDN . Isso vai funcionar, mas há problemas.



Por exemplo, temos um código que destrói completamente toda a nossa ideia, o que nos impede de fazer mais alguma coisa. Este é um código que bloqueia a execução. Algo precisa ser feito para que o usuário não possa bloquear o aplicativo. Por exemplo, um loop sem fim pode quebrar tudo. Se alert () e prompt () ainda puderem ser redefinidos, não será possível redefinir um loop infinito.

eval () é mau


Então chegamos ao ponto em que eval () é mau. Não é à toa que eles o chamam de mal, porque é uma função insidiosa que realmente incorpora tudo o mais livre e aberto que existe em JS e nos deixa completamente indefesos. Com uma função simples, fazemos um enorme buraco em nossa aplicação.



Mas e se eu disser, [na voz de Steve Jobs] que reinventamos o eval ()?

Fizemos eval () em outras tecnologias, ele funciona quase da mesma forma que já temos. Na verdade, eu tenho uma função eval () no meu código, mas implementada usando Workers, the with statement e Proxy.



Por que trabalhadores? O fato é que eles criam um thread de execução separado, ou seja, o JS é de thread único, mas, graças aos trabalhadores, podemos obter outro thread. Isso nos dá muitos benefícios. Por exemplo, dentro dos mesmos loops intermináveis, podemos interromper o thread criado através do worker a partir do thread principal, talvez seja esse o principal motivo pelo qual eu usei workers. Se o trabalhador conseguiu trabalhar mais rápido do que em um segundo, nós o consideramos bem-sucedido, obtemos seu resultado. Caso contrário, simplesmente cortamos. De fato, o código do usuário, por algum motivo, não funcionou, ocorreram alguns erros estranhos ou foram mais lentos devido a um loop infinito. Muitos hoje tentaram escrever enquanto (verdadeiro), avisei que isso não funcionaria.



Para escrever nosso trabalhador, precisamos apenas alimentar o script para o construtor do trabalhador, que será baixado via http. Dentro do script, precisamos criar um manipulador de mensagens a partir do thread principal. Usando a função postMessage () dentro do worker, podemos rotear mensagens para o thread principal. Dessa forma, fazemos a comunicação entre os dois threads. Uma API simples bastante conveniente, mas algo está faltando nela, ou seja, o código do usuário que devemos executar neste trabalhador. Não geraremos sempre um arquivo de script no servidor e o alimentaremos com o trabalhador.



Eu encontrei uma maneira usando URL.createObjectURL (). Fazemos um bloco e o alimentamos ao src worker. Assim, ele descarrega nosso código diretamente da linha. A propósito, dessa maneira funciona com qualquer objeto no DOM que possua src-image funciona assim, por exemplo, e mesmo em um iframe você pode carregar o html apenas gerando-o a partir de uma string. Muito legal e flexível, eu acho. Também podemos gerenciar o trabalhador simplesmente transmitindo a ele nosso objeto especialmente gerado a partir da URL. Também podemos encerrá-lo e ele já funciona como precisamos, e criamos a primeira sandbox.



As interações assíncronas vão mais longe, porque qualquer trabalho com trabalhadores é assíncrono. Enviamos alguma mensagem e não podemos esperar pela próxima mensagem de forma síncrona, o worker apenas retorna uma instância para nós e podemos assinar as mensagens. Pegamos a mensagem usando o RxJS, criamos dois threads: um para uma mensagem bem-sucedida do trabalhador, o segundo para conclusão por tempo limite. Dois threads que controlamos com sua mesclagem.



O RxJS possui operadores que nos permitem trabalhar com threads. De fato, é como um lodash para operações síncronas. Podemos indicar alguma função e não pensar em como ela é implementada por dentro, isso alivia a dor de cabeça. Precisamos começar a pensar em threads, o operador de mesclagem mescla nossos threads, responde a qualquer mensagem. Ele responderá ao tempo limite e à mensagem. Precisamos apenas da primeira mensagem, respectivamente, após a primeira mensagem que encerramos o worker. Em caso de erro, imprima esse erro; em caso de sucesso, resolvemos.



Tudo é bem simples aqui. Nosso código se torna declarativo, a complexidade da assincronia vai para algum lugar. O principal é aprender esses operadores.



É assim que trabalhamos com a API da unidade. Eu queria que a API da unidade fosse o mais simples possível. Falando sobre JS, muitas pessoas pensam que é difícil, você precisa subir em algum lugar, aprender alguma coisa. E eu queria simplificar o máximo possível: tudo na área global, existe apenas o escopo da API da unidade, nada mais. Tudo para gerenciamento de unidades, até preenchimento automático.



[Slide 15:20] A solução sugere que tudo isso pode ser jogado dentro do que é proibido com a declaração. Vamos entender por que é proibido.

O fato é que com tem seus problemas. Por exemplo, infelizmente, há vazamentos fora do escopo que lançamos nele, porque ele tenta parecer mais profundo que a API da unidade e analisa o escopo global.


Aqui o último exemplo é especialmente interessante, porque até quatro pode ser perigoso para o nosso código, pois todas essas funções podem ser executadas pelo código do usuário. O usuário pode fazer qualquer coisa. Este é um jogo para programadores e eles gostam de explorar os problemas e as maneiras de invadir alguma coisa.



Como eu disse, os escopos são muito vazados, portanto, um escopo global está sempre disponível. Independentemente de quantos escopos forneçamos em nosso código personalizado, não importa quantos escopos o agrupemos, o escopo global ainda estará visível. E tudo por causa de com.

De fato, ele não isola nada, apenas nos adiciona uma nova camada de abstração, um novo escopo global. Mas podemos mudar esse comportamento com o Proxy.



O fato é que o Proxy cuida de todas as nossas chamadas para o objeto em proxy por meio da nova API e podemos controlar como os novos pedidos de dados nesse objeto se comportam.



De fato, com funciona de maneira bastante simples. Quando o alimentamos com algum tipo de variável, ele verifica sob o capô se essa variável está no objeto (ou seja, executa o operador in) e, nesse caso, executa-o no objeto e, se não, executa no escopo superior, em Nosso caso é global. É bem simples aqui. A principal coisa que o proxy nos ajuda é que podemos substituir esse comportamento.



Existe algo como ganchos no Proxy. Uma coisa maravilhosa que nos permite fazer proxy de quaisquer solicitações para o objeto. Podemos alterar o comportamento da solicitação de atributo, alterar o comportamento da tarefa de atributo e, o mais importante, podemos alterar o comportamento disso no operador. Existe um gancho has, ao qual podemos retornar apenas true. Assim, enganamos e enganamos completamente nossa declaração with, tornando nossa API muito mais segura do que antes.



Se tentarmos executar eval (), ele primeiro perguntará se esse eval () está em unitApi, eles responderão "sim" e ficarão "indefinidos não é uma função". Esta parece ser a primeira vez que estou feliz com esse erro! Esse erro é exatamente o que deveríamos ter recebido. Pegamos e dissemos ao usuário: "Desculpe, esqueça tudo o que você sabia sobre o objeto janela, isso não é mais." Já deixamos alguns problemas, mas isso não é tudo.



O fato é que a declaração with é a mesma de JS, JS é dinâmico e um pouco estranho. O estranho é que nem tudo funciona como gostaríamos, sem olhar para a especificação. O fato é que também funciona com propriedades de protótipo. Ou seja, podemos brinca com um array, executar esse código obscuro. Todas as funções da matriz estão disponíveis como globais neste escopo, o que parece um pouco estranho.



Isso não é importante para nós, é importante para nós que o usuário possa executar valueOf () e obter toda a nossa sandbox. Pegue e pegue diretamente, veja o que está nele. Também não queria isso, então uma coisa interessante foi introduzida na especificação: Symbol.unscopables. Ou seja, na nova especificação de símbolos, Symbol.unscopables foi introduzido especificamente para a instrução with, que é proibida. Porque eles acreditam que alguém mais está usando. Por exemplo, eu!



Assim, faremos outro interceptador, onde verificamos especificamente se esse símbolo está na lista de todos os atributos que não podem ser copiados. Caso contrário, devolva-o, mas, nesse caso, desculpe, não retornaremos. Também não o usamos. E assim, com nós não podemos nem obter um protótipo de nossa caixa de areia.



Ainda temos o ambiente de trabalho. Isso é algo que trava em uma área global e ainda é acessível. O fato é que, se você simplesmente substituir isso, ele estará disponível no protótipo. Quase tudo pode ser extraído através do protótipo em JS. Surpreendentemente, todos esses métodos ainda estão disponíveis através do protótipo.



Eu só tinha que pegar e limpar tudo isso. Examinamos todas as chaves e limpamos tudo.



E então deixamos um pequeno ovo de Páscoa para o usuário, que ainda tenta chamar isso. Pegamos a função usual, o principal não é a função de seta, que tem um escopo, e alteramos seu escopo para nosso objeto, no qual deixamos um pequeno ovo de Páscoa para um usuário particularmente curioso que deseja exibir um pouco disso ou de si no console. Eu acredito que os ovos de Páscoa são maravilhosos e devem ser deixados no código.



Acontece ainda que eles foram deixados apenas com nossa API de unidade. Bloqueamos completamente tudo - na verdade, deixamos uma lista de permissões. Precisamos adicionar as APIs que são úteis e necessárias. Por exemplo, a API Math, que possui uma função aleatória útil que muitas pessoas usam ao escrever código para unidades.

Também precisamos de console e muitas outras funções utilitárias que não possuem nenhuma função destrutiva. Criamos uma lista while para nossas APIs. , blacklist, , .



whitelist, try-catch . , .



, Worker . , worker, , « , » . , JavaScript , .

, , - . , . , webpack .



patchMethod(), , postMessage(). postMessage() console log, error, warn, info. , . , <div>, , , , , .



, . : - - — , , - . , , promises. , actions, .



actions. , real-time ? , real-time workers , worker, . - , . , , . , , . .



workers, . workers , . , . , , ( , ), . : — .


Math.random()


, , , . Math.random().

, , , . , Math.random() - .



, , ( ), JS , . .



, , , . , - .

, . , random(), .



, random() — « » , . , - , random() , . - , . , , random() .



. , , random() . - seed ( , , ).


, . , random(). , random() -.

, worker, , JS . «» — . . , JS . random() unit API. , worker.



State sharing: RxJS,


Então, descobrimos o que temos com o cliente. Agora vamos falar sobre o compartilhamento de estado, por que isso é necessário e como foi organizado. Temos estado armazenado no servidor, o servidor deve atrapalhá-lo com os clientes conectados. [Slide 28:48]

Temos quatro funções de clientes diferentes que podem se conectar ao servidor: “usuário esquerdo”, “usuário certo”, um visualizador que olha para a tela principal e um administrador que pode fazer qualquer coisa.

A tela esquerda não pode alterar o estado do player certo, o visualizador não pode alterar nada e o administrador pode fazer tudo.



Por que isso foi um problema? Tudo é organizado de maneira simples. Qualquer cliente conectado pode lançar uma sessão, o servidor a aceita e se funde com o estado, que está dentro do servidor, e depois a distribui a todos os clientes. Ele se atrapalha com qualquer mudança que venha a ele. Era necessário filtrá-lo de alguma forma.



Primeiro, vou dizer por que o servidor também tem RxJS. Todas as interações com dois ou mais usuários conectados se tornam assíncronas. Devemos esperar pelos resultados de ambos os usuários. Por exemplo, os dois usuários clicaram no botão "Concluir", você deve esperar os dois clicarem e só então executar a ação. Foi tudo bastante direto no RxJS desta maneira:



Estamos novamente operando com threads, existe um fluxo do soquete, chamado soquete. Para criar um segmento que monitora apenas o jogador esquerdo, simplesmente pegamos e filtramos as mensagens desse soquete pelo jogador esquerdo (e da mesma forma que o direito). Em seguida, podemos combiná-los usando o operador forkJoin (), que funciona como Promise.all () e é seu análogo. Esperamos por essas duas ações e chamamos o método setState (), que define nosso estado como "pronto". Acontece que estamos esperando pelos dois jogadores e alteramos o estado do servidor. No RxJS, isso é o mais declarativo possível, e é por isso que eu o usei.

Ainda existe um problema com o fato de que os jogadores podem mudar de estado entre si. Eles devem ser proibidos de fazer isso. Ainda assim, eles são programadores, houve precedentes que alguém tentou. Vamos criar classes separadas para elas que são herdadas do Client.



Eles terão a lógica básica da comunicação do jogador com o servidor e, em cada classe em particular, haverá uma lógica personalizada para filtrar dados.

Cliente é na verdade um conjunto de conexões, conexões com clientes.



Ele apenas as armazena e ele possui o fluxo onUnsafeMessage, que é completamente inseguro: ele não pode ser confiável, são apenas mensagens brutas do usuário que ele recebe. Nós escrevemos essas mensagens brutas no fluxo.

Além disso, ao implementar um player específico, pegamos isso em UnsafeMessage e o filtramos.



Precisamos filtrar apenas os dados que podemos obter desse player, em quem podemos confiar. O jogador esquerdo só pode alterar o estado do jogador esquerdo, respectivamente, tiramos de todos os dados que ele poderia enviar, apenas o estado do jogador esquerdo. Se você não enviou, tudo bem. Se enviado - nós levamos. Assim, a partir de mensagens completamente inseguras, recebemos mensagens seguras nas quais podemos confiar ao trabalhar na sala.



Temos salas de jogos que reúnem jogadores. Dentro da sala, podemos escrever exatamente as funções que podem mudar de estado diretamente, simplesmente assinando esses fluxos, nos quais já podemos confiar. Nós abstraímos de um monte de cheques. Fizemos as verificações com base em funções e as chamamos de classes separadas. Dividimos o código de forma que, dentro do controlador, onde são executadas as importantes funções de mudança de estado, o código se tornou o mais simples e declarativo possível.

O RxJS também é usado no cliente, é conectado ao soquete no verso, emite eventos e os redireciona de todas as maneiras.

Nesse caso, gostaria de dar um exemplo quando precisar mudar o exército do adversário certo.



Para se inscrever, criamos um fluxo a partir do mesmo soquete e o filtramos. Garantimos que este é realmente o jogador certo e recebemos uma mensagem dele sobre qual é o seu exército. Se não houver essa mensagem, o fluxo não retornará nada, não haverá uma única mensagem, permanecerá em silêncio até que o jogador mude o exército. Nós imediatamente resolvemos declarativamente o problema de filtrar eventos, não o temos por brega.

E quando algo já veio do fluxo, chamamos a função setState (). Isso é bastante simples, a própria abordagem que nos permite fazer tudo de forma transparente e declarativa. Foi exatamente o que fiz no projeto RxJS e o que me ajudou muito.



Eu crio streams com os quais eu claramente nomeei, com os quais eu entendo como trabalhar, tudo é declarativo, as funções necessárias são chamadas, não há problemas com muitos eventos if e de filtragem, tudo isso é feito pelo RxJS.

História: do single player ao multiplayer



Então, a primeira versão do meu brinquedo foi escrita. Podemos dizer que era um único jogador, porque apenas dois jogadores podiam reproduzi-lo, o número de clientes conectados foi fixo. Tínhamos um jogador esquerdo, um jogador direito e a tela atrás, tudo isso conectado diretamente ao servidor. Tudo foi revestido, mas em três semanas foi feito.

Recebi uma nova oferta: expandir o brinquedo para todos os programadores da empresa para que eles possam abrir e jogar em seus computadores. Para que possamos obter uma lista de líderes, multiplayer, para que eles possam jogar juntos. Então percebi que tinha muita refatoração.



Acabou não sendo tão difícil. Simplesmente combinei todas as entidades que tinha em salas separadas. Eu tenho a essência de "Room", que poderia combinar todos os papéis. Agora, não são os próprios jogadores que se comunicam diretamente com o servidor, mas as salas. As salas já fizeram pedidos de proxy diretamente para o servidor, substituindo o estado, e o estado se tornou separado para cada sala.



Peguei e reescrevi tudo, adicionei uma lista de líderes, ganhamos os melhores com prêmios. Só era necessário ter um grande número de usuários, já era impossível seguir todos, era necessário escrever algo onde coletar todos os dados.

JS Gamedev e seus problemas



Assim, familiarizei-me mais seriamente com o JS-gamedev. Fiz uma discussão sobre o último projeto por cerca de três anos, descansando periodicamente. E aqui eu tive as duas vezes por três semanas. Todos os dias eu me sentava e fazia alguma coisa à noite.

Quais problemas existem no desenvolvimento de jogos em JS? Tudo é diferente de nossos aplicativos de negócios, onde não é um problema escrever algo do zero. Além disso, muito disso é bem-vindo: faremos nossas próprias coisas, lembraremos das histórias com a NPM e daremos o seu tapinha.



É impossível fazer isso no JS Gamedev, porque todas as tecnologias para exibir gráficos são tão baixas que é banal economicamente inútil escrever algo sobre eles. Se eu pegasse esse brinquedo e começasse a escrevê-lo do zero no WebGL, também ficaria sentado por cerca de seis meses, tentando descobrir alguns erros estranhos. O mecanismo de jogo mais popular da Phaser removeu esses problemas de mim ...



... e adicionei novos para mim: 5 megabytes em um pacote. E nada poderia ser feito sobre isso; ele não sabe o que é a mudança de árvores. Além disso, apenas a versão mais recente do Phaser pode funcionar com pacotes e webpack. Antes disso, Phaser era conectado apenas na tag html do script, era estranho para mim.

Eu venho de todos os tipos de scripts de webpacks e, no jogo JS, quase nada pode fazer isso. Todos os módulos têm uma digitação extremamente ruim ou não a possuem, ou basicamente não sabem como usar o webpack, foi necessário encontrar maneiras de envolvê-lo. Como se viu, mesmo o Ace Editor em sua forma pura não funciona com o webpack. Para começar a trabalhar, você precisa fazer o download de um pacote separado onde já está empacotado (chave).

Foi o mesmo com a Phaser, mas na nova versão eles fizeram isso mais ou menos normalmente. Continuei escrevendo na Phaser e descobri como fazer tudo funcionar com o webpack da maneira que costumávamos: tanto a digitação quanto os testes podiam ser anexados a tudo isso. Descobri que você pode pegar o PixiJS separadamente, que é uma renderização do webpack, e encontrar muitos módulos prontos para trabalhar com ele.



O PixiJS é uma ótima biblioteca que pode renderizar no WebGL ou no Canvas. Além disso, você pode até escrever código como se fosse para o Canvas, e ele será renderizado no WebGL. Esta biblioteca pode renderizar 2D muito rapidamente. O principal é saber como funciona com a memória, para não cair em uma posição quando a memória acabar.

Eu recomendo separadamente o repositório awesome-pixijs no GitHub, onde você pode encontrar diferentes módulos. Acima de tudo, gostei do React-pixi . Podemos simplesmente ignorar a solução de problemas com a visualização quando escrevemos funções imperativas diretamente no controlador para desenhar formas geométricas, sprites, animações e muito mais. Todos nós podemos marcar em JSX. Viemos do mundo JSX com nosso aplicativo de negócios e podemos usá-los ainda mais. É para isso que amo a abstração. React-pixi nos dá essa abstração familiar.

Também recomendo que você use o tween.js, o mesmo famoso mecanismo de animação da Phaser, que permite criar animações declarativas que são um pouco semelhantes às animações CSS: fazemos uma transição entre estados e o tween.js decide exatamente como mover o objeto.

Tipos de jogadores: quem são e como fazer amizade com eles


Me deparei com diferentes jogadores e também gostaria de falar sobre o teste do brinquedo. Reuni colegas em uma sala fechada e não os soltei até que terminassem o jogo. Infelizmente, nem todos conseguiram terminar o jogo, porque no começo eu tinha muitos bugs. Felizmente, comecei a testar assim que pelo menos algum protótipo em funcionamento apareceu. Honestamente, o primeiro teste falhou porque alguns jogadores não iniciaram nada. Foi uma pena, mas me deu um chute que me permitiu seguir em frente.

Quando seu brinquedo estiver pronto, você poderá ser recebido muito bem ou com um forcado e tochas. Todas as pessoas estão esperando por algum tipo de fã dos jogos, esperando a felicidade que você lhes dará. E você lhes dá algo que não funciona, embora pareça funcionar para você. Quando você tem um brinquedo on-line, existem ainda mais erros desse tipo.

Como resultado, as pessoas mais agradáveis ​​que encontrei são “pesquisadores” que sempre encontram mais em seu brinquedo do que realmente são. Eles podem complementá-lo agradavelmente com todo tipo de pequenas coisas, solicitando que você adicione algo. Infelizmente, a comunicação com essas pessoas não deu o importante - a estabilidade do brinquedo.

Existem jogadores comuns que vêm apenas para o bem dos fãs. Às vezes, eles nem percebem insetos, de alguma forma deslizando através deles a caminho do prazer.

Outra categoria são os coletores de erros, para os quais quase tudo não funciona. Você precisa ser amigo dessas pessoas, embora elas falem muita negatividade. Precisamos estabelecer relacionamentos estranhos com eles: eles machucam você e você está tentando tirar algo útil deles: "vamos sentar no seu computador e ver". Você precisa trabalhar com eles, porque no final são essas pessoas que farão a qualidade do seu jogo.

Você precisa testar apenas em pessoas vivas. Seu olho está embaçado e os testes certamente mostrarão o que está oculto. Você está desenvolvendo um brinquedo e mergulhando mais fundo, vendo alguns recursos, mas eles podem até não ser necessários. Você vai diretamente aos seus consumidores, mostra a eles e observa como eles tocam, quais teclas pressionam. Isso lhe dá um incentivo para fazer exatamente o que você precisa. Você vê que algumas pessoas pressionam constantemente Ctrl + S, porque estão acostumadas a salvar o código - bem, pelo menos faça o código rodar em Ctrl + S, o player se sentirá mais confortável. Você precisa criar um ambiente confortável para o jogador, para isso você precisa amá-lo e segui-lo.

A regra 80/20 funciona: você faz uma demonstração 20% do tempo com todo o desenvolvimento do jogo e, para o jogador, parece um jogo completo de 80%. A percepção funciona para que a mecânica básica esteja pronta, tudo se move e funciona, o que significa que o jogo está quase pronto, e o desenvolvedor terminará em breve. Mas, na realidade, o desenvolvedor ainda tem uma saída de 80%. Como eu disse, durante muito tempo tive que trabalhar na documentação para que fosse compreensível para todos. Eu mostrei para muitas pessoas que fizeram seus comentários, eu os filtrei, tentando entender a essência das declarações. E demorou muito tempo para procurar bugs.

Portanto, no desenvolvimento de jogos, eu só poderia aconselhá-lo a fazer demos: eles encantam a todos, não exigem muito tempo e ninguém espera realmente nada deles. Terminar jogos é um processo chato, mas é ótimo começar.

Por fim, deixo os links:

O HolyJS 2019 Piter , uma conferência para desenvolvedores de JavaScript, será realizada de 24 a 25 de maio em São Petersburgo. Os primeiros palestrantes já apareceram no site.
Você também pode solicitar um relatório, a Chamada de Trabalhos está aberta até 11 de março.
Os preços dos ingressos subirão em 1º de fevereiro.

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


All Articles