O que você precisa saber ao converter um jogo em Flash em HTML5?
Publicados: 2022-03-10Com o aumento do uso do HTML5, muitas empresas começam a refazer seus títulos mais populares para se livrar do Flash desatualizado e adequar seus produtos aos mais recentes padrões do setor. Essa mudança é especialmente visível nas indústrias de jogos de azar/cassino e entretenimento e vem acontecendo há vários anos, portanto, uma seleção decente de títulos já foi convertida.
Infelizmente, ao navegar na Internet, muitas vezes você pode se deparar com exemplos de um trabalho aparentemente apressado, que resulta na qualidade do produto final. É por isso que é uma boa ideia que os desenvolvedores de jogos dediquem um pouco de seu tempo para se familiarizar com o assunto de conversão de Flash para HTML5 e aprender quais erros devem ser evitados antes de começar a trabalhar.
Entre as razões para escolher JavaScript em vez de Flash, além dos problemas técnicos óbvios, também está o fato de que mudar o design do seu jogo de SWF para JavaScript pode resultar em uma melhor experiência do usuário, o que, por sua vez, dá uma aparência moderna. Mas como fazer isso? Você precisa de um conversor de jogos JavaScript dedicado para se livrar dessa tecnologia desatualizada? Bem, a conversão de Flash para HTML5 pode ser muito fácil - veja como cuidar disso.
Leitura recomendada : Princípios de design de jogos HTML5
Como melhorar a experiência de jogo HTML5
Converter um jogo para outra plataforma é uma excelente oportunidade para melhorá-lo, corrigir seus problemas e aumentar a audiência. Abaixo estão algumas coisas que podem ser feitas facilmente e vale a pena considerar:
Suportando dispositivos móveis
A conversão de Flash para JavaScript permite atingir um público mais amplo (usuários de dispositivos móveis); o suporte para controles de tela sensível ao toque geralmente também precisa ser implementado no jogo. Felizmente, os dispositivos Android e iOS agora também suportam WebGL, portanto, a renderização de 30 ou 60 FPS geralmente pode ser facilmente alcançada. Em muitos casos, 60 FPS não causará problemas, o que só melhorará com o tempo, à medida que os dispositivos móveis se tornarem cada vez mais eficientes.- Melhorando a performance
Quando se trata de comparar ActionScript e JavaScript, o último é mais rápido que o primeiro. Fora isso, converter um jogo é uma boa ocasião para revisitar algoritmos usados no código do jogo. Com o desenvolvimento de jogos JavaScript, você pode otimizá-los ou remover completamente o código não utilizado deixado pelos desenvolvedores originais. - Corrigindo bugs e fazendo melhorias na jogabilidade
Ter novos desenvolvedores analisando o código-fonte do jogo pode ajudar a corrigir bugs conhecidos ou descobrir novos e muito raros. Isso tornaria o jogo menos irritante para os jogadores, o que os faria passar mais tempo em seu site e os incentivaria a experimentar seus outros jogos. - Adicionando análise da web
Além de rastrear o tráfego, a análise da web também pode ser usada para reunir conhecimento sobre como os jogadores se comportam em um jogo e onde ficam presos durante o jogo. - Adicionando localização
Isso aumentaria o público e é importante para crianças de outros países que jogam seu jogo. Ou talvez seu jogo não esteja em inglês e você queira oferecer suporte a esse idioma?
Por que pular HTML e CSS para a interface do usuário no jogo melhorará o desempenho do jogo
Quando se trata de desenvolvimento de jogos JavaScript, pode ser tentador aproveitar HTML e CSS para botões, widgets e outros elementos da GUI no jogo. Meu conselho é ter cuidado aqui. É contra-intuitivo, mas, na verdade, alavancar elementos DOM tem menos desempenho em jogos complexos e isso ganha mais significado em dispositivos móveis. Se você deseja atingir 60 FPS constantes em todas as plataformas, pode ser necessário renunciar ao HTML e ao CSS.
Elementos de GUI não interativos, como barras de integridade, barras de munição ou contadores de pontuação podem ser facilmente implementados no Phaser usando imagens regulares (a classe Phaser.Image
), aproveitando a propriedade .crop
para aparar e a classe Phaser.Text
para rótulos de texto.
Tais elementos interativos como botões e caixas de seleção podem ser implementados usando a classe Phaser.Button
. Outros elementos mais complexos podem ser compostos de diferentes tipos simples, como grupos, imagens, botões e rótulos de texto.
Nota: Cada vez que você instancia um objeto Phaser.Text ou PIXI.Text, uma nova textura é criada para renderizar o texto. Essa textura adicional quebra o lote de vértices, portanto, tome cuidado para não ter muitos deles .
Como garantir que as fontes personalizadas foram carregadas
Se você deseja renderizar texto com uma fonte vetorial personalizada (por exemplo, TTF ou OTF), você precisa garantir que a fonte já tenha sido carregada pelo navegador antes de renderizar qualquer texto. Phaser v2 não oferece uma solução para este propósito, mas outra biblioteca pode ser usada: Web Font Loader.
Supondo que você tenha um arquivo de fonte e inclua o Web Font Loader em sua página, abaixo está um exemplo simples de como carregar uma fonte:
Faça um arquivo CSS simples que será carregado pelo Web Font Loader (você não precisa incluí-lo em seu HTML):
@font-face { // This name you will use in JS font-family: 'Gunplay'; // URL to the font file, can be relative or absolute src: url('../fonts/gunplay.ttf') format('truetype'); font-weight: 400; }
Agora defina uma variável global chamada WebFontConfig
. Algo tão simples como isso geralmente será suficiente:
var WebFontConfig = { 'classes': false, 'timeout': 0, 'active': function() { // The font has successfully loaded... }, 'custom': { 'families': ['Gunplay'], // URL to the previously mentioned CSS 'urls': ['styles/fonts.css'] } };
No final, lembre-se de colocar seu código no callback 'ativo' mostrado acima. E é isso!
Como tornar mais fácil para os usuários salvar o jogo
Para armazenar dados locais de forma persistente no ActionScript, você usaria a classe SharedObject. Em JavaScript, a substituição simples é a API localStorage, que permite armazenar strings para recuperação posterior, sobrevivendo a recargas de página.
Salvar dados é muito simples:
var progress = 15; localStorage.setItem('myGame.progress', progress);
Observe que no exemplo acima a variável progress
, que é um número, será convertida em uma string.
O carregamento também é simples, mas lembre-se de que os valores recuperados serão strings ou null
se não existirem.
var progress = parseInt(localStorage.getItem('myGame.progress')) || 0;
Aqui estamos garantindo que o valor de retorno seja um número. Se não existir, 0 será atribuído à variável de progress
.
Você também pode armazenar e recuperar estruturas mais complexas, por exemplo, JSON:
var stats = {'goals': 13, 'wins': 7, 'losses': 3, 'draws': 1}; localStorage.setItem('myGame.stats', JSON.stringify(stats)); … var stats = JSON.parse(localStorage.getItem('myGame.stats')) || {};
Existem alguns casos em que o objeto localStorage não estará disponível. Por exemplo, ao usar o protocolo file://
ou quando uma página é carregada em uma janela privada. Você pode usar a instrução try e catch para garantir que seu código continue funcionando e use valores padrão, o que é mostrado no exemplo abaixo:
try { var progress = localStorage.getItem('myGame.progress'); } catch (exception) { // localStorage not available, use default values }
Outra coisa a lembrar é que os dados armazenados são salvos por domínio, não por URL. Portanto, se houver o risco de muitos jogos serem hospedados em um único domínio, é melhor usar um prefixo (namespace) ao salvar. No exemplo acima 'myGame.'
é um prefixo e você geralmente deseja substituí-lo pelo nome do jogo.
Observação : se o seu jogo estiver incorporado em um iframe, localStorage não persistirá no iOS. Nesse caso, você precisaria armazenar dados no iframe pai .
Como aproveitar a substituição do sombreador de fragmento padrão
Quando Phaser e PixiJS renderizam seus sprites, eles usam um sombreador de fragmento interno simples. Ele não tem muitos recursos porque é adaptado para uma velocidade. No entanto, você pode substituir esse sombreador para seus propósitos. Por exemplo, você pode aproveitá-lo para inspecionar overdraw ou dar suporte a mais recursos para renderização.
Abaixo está um exemplo de como fornecer seu próprio sombreador de fragmento padrão para Phaser v2:
function preload() { this.load.shader('filename.frag', 'shaders/filename.frag'); } function create() { var renderer = this.renderer; var batch = renderer.spriteBatch; batch.defaultShader = new PIXI.AbstractFilter(this.cache.getShader('filename.frag')); batch.setContext(renderer.gl); }
Nota: É importante lembrar que o shader padrão é usado para TODOS os sprites, bem como ao renderizar para uma textura. Além disso, lembre-se de que usar shaders complexos para todos os sprites do jogo reduzirá bastante o desempenho de renderização .
Como alterar o método de coloração com um sombreador padrão
O sombreador padrão personalizado pode ser usado para substituir o método de coloração padrão em Phaser e PixiJS.
A coloração em Phaser e PixiJS funciona multiplicando pixels de textura por uma determinada cor. A multiplicação sempre escurece as cores, o que obviamente não é um problema; é simplesmente diferente da coloração do Flash. Para um de nossos jogos, precisávamos implementar uma coloração semelhante ao Flash e decidimos que um shader padrão personalizado poderia ser usado. Abaixo está um exemplo de tal sombreador de fragmento:
// Specific tint variant, similar to the Flash tinting that adds // to the color and does not multiply. A negative of a color // must be supplied for this shader to work properly, ie set // sprite.tint to 0 to turn whole sprite to white. precision lowp float; varying vec2 vTextureCoord; varying vec4 vColor; uniform sampler2D uSampler; void main(void) { vec4 f = texture2D(uSampler, vTextureCoord); float a = clamp(vColor.a, 0.00001, 1.0); gl_FragColor.rgb = f.rgb * vColor.a + clamp(1.0 - vColor.rgb/a, 0.0, 1.0) * vColor.a * fa; gl_FragColor.a = fa * vColor.a; }
Este sombreador clareia os pixels adicionando uma cor base à tonalidade. Para que isso funcione, você precisa fornecer negativo da cor desejada. Portanto, para obter branco, você precisa definir:
sprite.tint = 0x000000; // This colors the sprite to white Sprite.tint = 0x00ffff; // This gives red
O resultado em nosso jogo é assim (observe como os tanques piscam em branco quando atingidos):
Como inspecionar overdraw para detectar problemas de taxa de preenchimento
A substituição do sombreador padrão também pode ser aproveitada para ajudar na depuração. Abaixo, expliquei como o overdraw pode ser detectado com esse sombreador.
O overdrawing acontece quando muitos ou todos os pixels na tela são renderizados várias vezes. Por exemplo, muitos objetos ocupando o mesmo lugar e sendo renderizados um sobre o outro. Quantos pixels uma GPU pode renderizar por segundo é descrito como taxa de preenchimento. As GPUs de desktop modernas têm taxa de preenchimento excessiva para fins 2D usuais, mas as móveis são muito mais lentas.
Existe um método simples de descobrir quantas vezes cada pixel na tela é gravado substituindo o sombreador de fragmento global padrão no PixiJS e no Phaser por este:
void main(void) { gl_FragColor.rgb += 1.0 / 7.0; }
Este sombreador clareia os pixels que estão sendo processados. O número 7.0 indica quantas gravações são necessárias para tornar o pixel branco; você pode ajustar este número ao seu gosto. Em outras palavras, pixels mais claros na tela foram gravados várias vezes e pixels brancos foram gravados pelo menos 7 vezes.
Esse sombreador também ajuda a encontrar objetos “invisíveis” que por algum motivo ainda são renderizados e sprites que possuem áreas transparentes excessivas ao redor que precisam ser removidas (a GPU ainda precisa processar pixels transparentes em suas texturas).
A imagem da esquerda mostra como um jogador vê o jogo, enquanto a da direita mostra o efeito de aplicar o overdraw shader na mesma cena.
Por que os motores de física são seus amigos
Um motor de física é um middleware responsável por simular corpos físicos (geralmente dinâmica de corpo rígido) e suas colisões. Os motores de física simulam espaços 2D ou 3D, mas não ambos. Um mecanismo de física típico fornecerá:
- movimento de objetos definindo velocidades, acelerações, articulações e motores;
- detectar colisões entre vários tipos de formas;
- calcular as respostas de colisão, ou seja, como dois objetos devem reagir quando colidem.
No Merixstudio, somos grandes fãs do mecanismo de física Box2D e o usamos em algumas ocasiões. Existe um plugin Phaser que funciona bem para esta finalidade. Box2D também é usado no motor de jogo Unity e GameMaker Studio 2.
Embora um mecanismo de física acelere seu desenvolvimento, há um preço que você terá que pagar: desempenho de tempo de execução reduzido. Detectar colisões e calcular respostas é uma tarefa que consome muita CPU. Você pode estar limitado a várias dúzias de objetos dinâmicos em uma cena em telefones celulares ou enfrentar um desempenho degradado, bem como uma taxa de quadros reduzida abaixo de 60 FPS.
A parte esquerda da imagem é uma cena de um jogo, enquanto o lado direito mostra a mesma cena com a sobreposição de depuração física do Phaser exibida na parte superior.
Como exportar sons de um arquivo .fla
Se você tiver efeitos sonoros de jogos em Flash dentro de um arquivo .fla , não será possível exportá-los da GUI (pelo menos não no Adobe Animate CC 2017) devido à falta de opções de menu que atendam a esse propósito. Mas há outra solução - um script dedicado que faz exatamente isso:
function normalizeFilename(name) { // Converts a camelCase name to snake_case name return name.replace(/([AZ])/g, '_$1').replace(/^_/, '').toLowerCase(); } function displayPath(path) { // Makes the file path more readable return unescape(path).replace('file:///', '').replace('|', ':'); } fl.outputPanel.clear(); if (fl.getDocumentDOM().library.getSelectedItems().length > 0) // Get only selected items var library = fl.getDocumentDOM().library.getSelectedItems(); else // Get all items var library = fl.getDocumentDOM().library.items; // Ask user for the export destination directory var root = fl.browseForFolderURL('Select a folder.'); var errors = 0; for (var i = 0; i < library.length; i++) { var item = library[i]; if (item.itemType !== 'sound') continue; var path = root + '/'; if (item.originalCompressionType === 'RAW') path += normalizeFilename(item.name.split('.')[0]) + '.wav'; else path += normalizeFilename(item.name); var success = item.exportToFile(path); if (!success) errors += 1; fl.trace(displayPath(path) + ': ' + (success ? 'OK' : 'Error')); } fl.trace(errors + ' error(s)');
Como usar o script para exportar arquivos de som:
- Salve o código acima como um arquivo .jsfl em seu computador;
- Abra um arquivo .fla com o Adobe Animate;
- Selecione 'Comandos' → 'Executar Comando' no menu superior e selecione o script na caixa de diálogo que se abre;
- Agora, outro arquivo de diálogo aparece para selecionar o diretório de destino de exportação.
E feito! Agora você deve ter arquivos WAV no diretório especificado. O que resta a fazer é convertê-los para, por exemplo, MP3, OGG ou AAC.
Como usar MP3s em conversões de Flash para HTML5
O bom e velho formato MP3 está de volta, pois algumas patentes expiraram e agora todos os navegadores podem decodificar e reproduzir MP3. Isso torna o desenvolvimento um pouco mais fácil, pois finalmente não há necessidade de preparar dois formatos de áudio separados. Anteriormente, você precisava, por exemplo, de arquivos OGG e AAC, enquanto agora o MP3 será suficiente.
No entanto, há duas coisas importantes que você precisa lembrar sobre o MP3:
- O MP3 precisa decodificar após o carregamento, o que pode ser demorado, especialmente em dispositivos móveis. Se você vir uma pausa após o carregamento de todos os seus recursos, provavelmente significa que o MP3 está sendo decodificado;
- A reprodução contínua de MP3 em loop é um pouco problemática. A solução é usar o mp3loop, sobre o qual você pode ler no artigo postado pela Compu Phase.
Então, por que você deve converter Flash para JavaScript?
Como você pode ver, a conversão de Flash para JavaScript não é impossível se você souber o que fazer. Com conhecimento e habilidade, você pode parar de lutar com o Flash e aproveitar os jogos suaves e divertidos criados em JavaScript. Não tente consertar o Flash - livre-se dele antes que todos sejam forçados a fazê-lo!
Quer Aprender Mais?
Neste artigo, concentrei-me principalmente no Phaser v2. No entanto, uma versão mais recente do Phaser já está disponível, e eu recomendo fortemente que você dê uma olhada, pois introduziu uma infinidade de recursos novos e interessantes, como várias câmeras, cenas, tilemaps ou o mecanismo de física Matter.js.
Se você é corajoso o suficiente e quer criar coisas realmente notáveis em navegadores, então o WebGL é a coisa certa para aprender desde o início. É um nível de abstração mais baixo do que vários frameworks ou ferramentas de construção de jogos, mas permite obter maior desempenho e qualidade, mesmo se você trabalhar em jogos 2D ou demos. Entre muitos sites que você pode achar úteis ao aprender os fundamentos do WebGL estão os Fundamentos do WebGL (usa demonstrações interativas). Além disso, para saber mais sobre as taxas de adoção de recursos WebGL, consulte WebGL Stats.
Lembre-se sempre de que não existe conhecimento demais — especialmente quando se trata de desenvolvimento de jogos!