¿Qué necesita saber al convertir un juego Flash en HTML5?

Publicado: 2022-03-10
Resumen rápido ↬ Los consejos presentados en este artículo tienen como objetivo ayudar a los desarrolladores de juegos HTML5 a evitar errores comunes al convertir sus juegos Flash a JavaScript, además de hacer que todo el proceso de desarrollo se desarrolle de la mejor manera posible. Se requieren conocimientos básicos de JavaScript, WebGL y el marco Phaser.

Con el aumento del uso de HTML5, muchas empresas comienzan a rehacer sus títulos más populares para deshacerse de Flash obsoleto y adaptar sus productos a los últimos estándares de la industria. Este cambio es especialmente visible en las industrias de juegos de azar/casino y entretenimiento y ha estado ocurriendo durante varios años, por lo que ya se ha convertido una selección decente de títulos.

Desafortunadamente, cuando navega por Internet, a menudo puede tropezar con ejemplos de un trabajo aparentemente apresurado, lo que resulta en la calidad del amante del producto final. Por eso es una buena idea que los desarrolladores de juegos dediquen parte de su tiempo a familiarizarse con el tema de la conversión de Flash a HTML5 y aprender qué errores evitar antes de ponerse manos a la obra.

Entre las razones para elegir JavaScript en lugar de Flash, además de los problemas técnicos obvios, también está el hecho de que cambiar el diseño de su juego de SWF a JavaScript puede brindar una mejor experiencia de usuario, lo que a su vez le da un aspecto moderno. ¿Pero como hacerlo? ¿Necesita un convertidor de juegos de JavaScript dedicado para deshacerse de esta tecnología obsoleta? Bueno, la conversión de Flash a HTML5 puede ser pan comido: así es como se soluciona.

Lectura recomendada : Principios del diseño de juegos HTML5

Cómo mejorar la experiencia de juego HTML5

Convertir un juego a otra plataforma es una excelente oportunidad para mejorarlo, solucionar sus problemas y aumentar la audiencia. A continuación hay algunas cosas que se pueden hacer fácilmente y que vale la pena considerar:

  • Compatibilidad con dispositivos móviles
    La conversión de Flash a JavaScript permite llegar a un público más amplio (usuarios de dispositivos móviles); La compatibilidad con los controles de pantalla táctil generalmente también debe implementarse en el juego. Afortunadamente, tanto los dispositivos Android como iOS ahora también son compatibles con WebGL, por lo que, por lo general, se puede lograr fácilmente un renderizado de 30 o 60 FPS. En muchos casos, 60 FPS no causarán ningún problema, que solo mejorará con el tiempo, a medida que los dispositivos móviles se vuelvan cada vez más eficientes.

  • Mejorando el desempeño
    Cuando se trata de comparar ActionScript y JavaScript, este último es más rápido que el primero. Aparte de eso, convertir un juego es una buena ocasión para revisar los algoritmos utilizados en el código del juego. Con el desarrollo de juegos de JavaScript, puede optimizarlos o eliminar por completo el código no utilizado que dejaron los desarrolladores originales.
  • Corrección de errores y mejoras en el juego.
    Tener nuevos desarrolladores investigando el código fuente del juego puede ayudar a corregir errores conocidos o descubrir otros nuevos y muy raros. Esto haría que jugar el juego fuera menos irritante para los jugadores, lo que haría que pasaran más tiempo en su sitio y los alentaría a probar sus otros juegos.
  • Adición de análisis web
    Además de rastrear el tráfico, el análisis web también se puede utilizar para recopilar información sobre cómo se comportan los jugadores en un juego y dónde se atascan durante el juego.
  • Agregar localización
    Esto aumentaría la audiencia y es importante para los niños de otros países que juegan tu juego. ¿O tal vez tu juego no está en inglés y quieres admitir ese idioma?
¡Más después del salto! Continúe leyendo a continuación ↓

Por qué omitir HTML y CSS para la interfaz de usuario en el juego mejorará el rendimiento del juego

Cuando se trata del desarrollo de juegos con JavaScript, puede ser tentador aprovechar HTML y CSS para los botones, widgets y otros elementos de la GUI del juego. Mi consejo es tener cuidado aquí. Es contrario a la intuición, pero en realidad aprovechar los elementos DOM tiene menos rendimiento en juegos complejos y esto gana más importancia en dispositivos móviles. Si desea lograr 60 FPS constantes en todas las plataformas, es posible que deba renunciar a HTML y CSS.

Los elementos de GUI no interactivos, como barras de salud, barras de munición o contadores de puntuación, se pueden implementar fácilmente en Phaser mediante el uso de imágenes normales (la clase Phaser.Image ), aprovechando la propiedad .crop para recortar y la clase Phaser.Text para simplificar etiquetas de texto

Elementos interactivos como botones y casillas de verificación se pueden implementar mediante la clase Phaser.Button . Otros elementos más complejos pueden estar compuestos por diferentes tipos simples, como grupos, imágenes, botones y etiquetas de texto.

Nota: cada vez que crea una instancia de un objeto Phaser.Text o PIXI.Text, se crea una nueva textura para representar el texto. Esta textura adicional rompe el procesamiento por lotes de vértices, así que tenga cuidado de no tener demasiados .

Cómo asegurarse de que las fuentes personalizadas se hayan cargado

Si desea representar texto con una fuente vectorial personalizada (por ejemplo, TTF u OTF), debe asegurarse de que el navegador ya haya cargado la fuente antes de representar cualquier texto. Phaser v2 no proporciona una solución para este propósito, pero se puede usar otra biblioteca: Web Font Loader.

Suponiendo que tiene un archivo de fuente e incluye Web Font Loader en su página, a continuación se muestra un ejemplo simple de cómo cargar una fuente:

Crea un archivo CSS simple que será cargado por Web Font Loader (no necesitas incluirlo en tu 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; }

Ahora defina una variable global llamada WebFontConfig . Algo tan simple como esto suele 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'] } };

Al final, recuerde poner su código en la devolución de llamada 'activa' que se muestra arriba. ¡Y eso es!

Cómo hacer que sea más fácil para los usuarios guardar el juego

Para almacenar datos locales de forma persistente en ActionScript, usaría la clase SharedObject. En JavaScript, el reemplazo simple es la API localStorage, que permite almacenar cadenas para su posterior recuperación, sobreviviendo a las recargas de la página.

Guardar datos es muy simple:

 var progress = 15; localStorage.setItem('myGame.progress', progress);

Tenga en cuenta que en el ejemplo anterior, la variable de progress , que es un número, se convertirá en una cadena.

La carga también es simple, pero recuerde que los valores recuperados serán cadenas o null si no existen.

 var progress = parseInt(localStorage.getItem('myGame.progress')) || 0;

Aquí nos aseguramos de que el valor devuelto sea un número. Si no existe, se asignará 0 a la variable de progress .

También puede almacenar y recuperar estructuras más complejas, por ejemplo, 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')) || {};

Hay algunos casos en los que el objeto localStorage no estará disponible. Por ejemplo, cuando se usa el protocolo file:// o cuando se carga una página en una ventana privada. Puede usar la instrucción try and catch para asegurarse de que su código continúe funcionando y use valores predeterminados, como se muestra en el siguiente ejemplo:

 try { var progress = localStorage.getItem('myGame.progress'); } catch (exception) { // localStorage not available, use default values }

Otra cosa para recordar es que los datos almacenados se guardan por dominio, no por URL. Entonces, si existe el riesgo de que muchos juegos estén alojados en un solo dominio, entonces es mejor usar un prefijo (espacio de nombres) al guardar. En el ejemplo anterior 'myGame.' es un prefijo de este tipo y normalmente querrás reemplazarlo con el nombre del juego.

Nota : si su juego está incrustado en un iframe, localStorage no persistirá en iOS. En este caso, deberá almacenar datos en el iframe principal .

Cómo aprovechar la sustitución del sombreador de fragmentos predeterminado

Cuando Phaser y PixiJS renderizan tus sprites, usan un sombreador de fragmento interno simple. No tiene muchas características porque está diseñado para una velocidad. Sin embargo, puede reemplazar ese sombreador para sus propósitos. Por ejemplo, puede aprovecharlo para inspeccionar sobredibujado o admitir más funciones para renderizar.

A continuación se muestra un ejemplo de cómo proporcionar su propio sombreador de fragmentos predeterminado a 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: es importante recordar que el sombreador predeterminado se usa para TODOS los sprites, así como cuando se renderiza una textura. Además, tenga en cuenta que el uso de sombreadores complejos para todos los sprites del juego reducirá en gran medida el rendimiento del renderizado .

Cómo cambiar el método de teñido con un sombreador predeterminado

El sombreador predeterminado personalizado se puede usar para reemplazar el método de teñido predeterminado en Phaser y PixiJS.

El teñido en Phaser y PixiJS funciona multiplicando los píxeles de textura por un color determinado. La multiplicación siempre oscurece los colores, lo que obviamente no es un problema; es simplemente diferente del tinte Flash. Para uno de nuestros juegos, necesitábamos implementar un tinte similar a Flash y decidimos que se podía usar un sombreador predeterminado personalizado. A continuación se muestra un ejemplo de dicho sombreador de fragmentos:

 // 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 aclara los píxeles agregando un color base al tinte. Para que esto funcione, debe proporcionar el negativo del color que desea. Por lo tanto, para obtener blanco, debe configurar:

 sprite.tint = 0x000000; // This colors the sprite to white Sprite.tint = 0x00ffff; // This gives red

El resultado en nuestro juego se ve así (observe cómo los tanques parpadean en blanco cuando son golpeados):

Ejemplo del sombreador predeterminado personalizado en el desarrollo de juegos
Shader predeterminado personalizado (tanques que parpadean en blanco).

Cómo inspeccionar el sobregiro para detectar problemas de tasa de llenado

También se puede aprovechar la sustitución del sombreador predeterminado para ayudar con la depuración. A continuación, expliqué cómo se puede detectar el sobredimensionado con un sombreador de este tipo.

El sobredibujo ocurre cuando muchos o todos los píxeles de la pantalla se procesan varias veces. Por ejemplo, muchos objetos que toman el mismo lugar y se renderizan uno sobre otro. La cantidad de píxeles que una GPU puede procesar por segundo se describe como tasa de llenado. Las GPU de escritorio modernas tienen una tasa de llenado excesiva para los propósitos 2D habituales, pero las móviles son mucho más lentas.

Existe un método simple para averiguar cuántas veces se escribe cada píxel en la pantalla al reemplazar el sombreador de fragmentos global predeterminado en PixiJS y Phaser con este:

 void main(void) { gl_FragColor.rgb += 1.0 / 7.0; }

Este sombreador aclara los píxeles que se están procesando. El número 7.0 indica cuántas escrituras se necesitan para convertir el píxel en blanco; puede sintonizar este número a su gusto. En otras palabras, los píxeles más claros en la pantalla se escribieron varias veces y los píxeles blancos se escribieron al menos 7 veces.

Este shader también ayuda a encontrar tanto objetos "invisibles" que por alguna razón todavía están renderizados como sprites que tienen áreas transparentes excesivas alrededor que necesitan ser eliminadas (la GPU aún necesita procesar píxeles transparentes en sus texturas).

Ejemplo del shader Overdraw en acción en el desarrollo de juegos
Overdraw shader en acción. (Vista previa grande)

La imagen de la izquierda muestra cómo un jugador ve el juego, mientras que la de la derecha muestra el efecto de aplicar el sombreador de sobredibujado a la misma escena.

Por qué los motores de física son tus amigos

Un motor de física es un middleware responsable de simular cuerpos físicos (generalmente dinámicas de cuerpos rígidos) y sus colisiones. Los motores de física simulan espacios 2D o 3D, pero no ambos. Un motor de física típico proporcionará:

  • movimiento de objetos mediante el establecimiento de velocidades, aceleraciones, articulaciones y motores;
  • detectar colisiones entre varios tipos de formas;
  • calcular las respuestas de colisión, es decir, cómo deben reaccionar dos objetos cuando chocan.

En Merixstudio, somos grandes admiradores del motor de física Box2D y lo usamos en algunas ocasiones. Hay un complemento de Phaser que funciona bien para este propósito. Box2D también se usa en el motor de juego Unity y GameMaker Studio 2.

Si bien un motor de física acelerará su desarrollo, hay un precio que tendrá que pagar: rendimiento de tiempo de ejecución reducido. La detección de colisiones y el cálculo de respuestas es una tarea que requiere un uso intensivo de la CPU. Puede estar limitado a varias docenas de objetos dinámicos en una escena en teléfonos móviles o enfrentar un rendimiento degradado, así como una velocidad de cuadros reducida por debajo de 60 FPS.

Ejemplo de la diferencia en la escena de un juego con y sin nuestra superposición de depuración de física de Phaser que se muestra en la parte superior
Superposición de depuración de física de Phaser. (Vista previa grande)

La parte izquierda de la imagen es una escena de un juego, mientras que el lado derecho muestra la misma escena con la superposición de depuración de física de Phaser en la parte superior.

Cómo exportar sonidos desde un archivo .fla

Si tiene efectos de sonido de un juego Flash dentro de un archivo .fla , entonces no es posible exportarlos desde la GUI (al menos no en Adobe Animate CC 2017) debido a la falta de opciones de menú para este propósito. Pero hay otra solución: un script dedicado que hace precisamente eso:

 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)');

Cómo usar el script para exportar archivos de sonido:

  1. Guarde el código anterior como un archivo .jsfl en su computadora;
  2. Abra un archivo .fla con Adobe Animate;
  3. Seleccione 'Comandos' → 'Ejecutar comando' en el menú superior y seleccione el script en el diálogo que se abre;
  4. Ahora aparece otro archivo de diálogo para seleccionar el directorio de destino de exportación.

¡Y hecho! Ahora debería tener archivos WAV en el directorio especificado. Lo que queda por hacer es convertirlos, por ejemplo, a MP3, OGG o AAC.

Cómo usar MP3 en conversiones de Flash a HTML5

El buen formato MP3 ha vuelto, ya que algunas patentes han expirado y ahora todos los navegadores pueden decodificar y reproducir MP3. Esto facilita un poco el desarrollo ya que finalmente no hay necesidad de preparar dos formatos de audio separados. Antes necesitabas, por ejemplo, archivos OGG y AAC, mientras que ahora bastará con MP3.

No obstante, hay dos cosas importantes que debe recordar acerca de MP3:

  • Los MP3 necesitan decodificarse después de cargarlos, lo que puede llevar mucho tiempo, especialmente en dispositivos móviles. Si ve una pausa después de que se hayan cargado todos sus recursos, probablemente signifique que se está decodificando el MP3;
  • La reproducción continua de MP3 en bucle es un poco problemática. La solución es usar mp3loop, sobre lo que puedes leer en el artículo publicado por Compu Phase.

Entonces, ¿por qué debería convertir Flash a JavaScript?

Como puede ver, la conversión de Flash a JavaScript no es imposible si sabe qué hacer. Con conocimiento y habilidad, puede dejar de luchar con Flash y disfrutar de juegos fluidos y entretenidos creados en JavaScript. No intente arreglar Flash: ¡deshágase de él antes de que todos se vean obligados a hacerlo!

¿Querer aprender más?

En este artículo, me centré principalmente en Phaser v2. Sin embargo, ahora está disponible una versión más nueva de Phaser, y le recomiendo encarecidamente que la pruebe, ya que introdujo una gran cantidad de funciones nuevas y geniales, como múltiples cámaras, escenas, mapas de mosaicos o el motor de física Matter.js.

Si eres lo suficientemente valiente y quieres crear cosas verdaderamente notables en los navegadores, entonces WebGL es lo correcto para aprender desde cero. Es un nivel de abstracción más bajo que varios marcos o herramientas de creación de juegos, pero permite lograr un mayor rendimiento y calidad incluso si trabaja en juegos o demostraciones 2D. Entre muchos sitios web que puede encontrar útiles al aprender los conceptos básicos de WebGL, se encuentran WebGL Fundamentals (usa demostraciones interactivas). Además de eso, para obtener más información sobre las tasas de adopción de características de WebGL, consulte Estadísticas de WebGL.

Recuerde siempre que no existe el exceso de conocimiento, ¡especialmente cuando se trata de desarrollo de juegos!