Cómo maximizar el rendimiento de HTML5
Sección 1: Principios básicos de ejecución de código
LayaAir
El motor admite el desarrollo en tres lenguajes: AS3, TypeScript y JavaScript, sin importar cuál. Se utiliza un lenguaje de desarrollo y, en última instancia, se ejecuta todo el código JavaScript.
Todas las imágenes que ves son dibujadas por el motor y la frecuencia de actualización depende del FPS especificado por el desarrollador. Por ejemplo, si la frecuencia de cuadro especificada es 60 FPS, el tiempo de ejecución de cada cuadro durante el tiempo de ejecución. es sesenta minutos y 1 segundo, por lo que cuanto mayor sea la velocidad de fotogramas, más fluida será la experiencia visual, y 60 fotogramas es un fotograma completo.
Dado que el entorno de ejecución real es el navegador, el rendimiento también depende de la eficiencia del intérprete de JavaScript. Es posible que la velocidad de fotogramas FPS especificada no se logre en un intérprete de bajo rendimiento, por lo que esta parte no. para desarrolladores Lo que los desarrolladores pueden decidir es optimizar tanto como sea posible para aumentar la velocidad de fotogramas de FPS en dispositivos de gama baja o navegadores de bajo rendimiento.
El motor LayaAir vuelve a dibujar cada cuadro. Al optimizar el rendimiento, además de prestar atención al consumo de CPU causado por la ejecución de código lógico en cada cuadro, también debe prestar atención a la cantidad de instrucciones de dibujo llamadas. cada fotograma y el número de envíos de texturas a la GPU.
Sección 2: Prueba comparativa
La herramienta de estadísticas de rendimiento integrada en el motor LayaAir se puede utilizar para pruebas comparativas para detectar el rendimiento actual en tiempo real. Los desarrolladores pueden usar la clase laya.utils.Stat para mostrar el panel de estadísticas a través de Stat.show(). El código específico es el siguiente:
1
2
Stat.show(0, 0); //Método de escritura de llamadas al panel AS3
Laya.Stat.show(0, 0); //Cómo escribir llamadas al panel en TS y JS
Estadísticas de renderizado de Canvas:
Estadísticas de renderizado de WebGL:
El significado de los parámetros estadísticos:
FPS:
El número de fotogramas renderizados por segundo (cuanto mayor sea el número, mejor).
Cuando se utiliza la representación del lienzo, el campo de descripción se muestra como FPS (Canvas), y cuando se utiliza la representación WebGL, el campo de descripción se muestra como FPS (WebGL).
Sprite:
El número de nodos de renderizado (cuanto menor sea el número, mejor).
Sprite cuenta todos los nodos de representación (incluidos los contenedores). El tamaño de este número afectará la cantidad de recorridos de los nodos del motor, la organización de los datos y la representación.
DrawCall:
DrawCall representa diferentes significados bajo el lienzo y la representación WebGL (cuanto menos, mejor).
En Lienzo, indica el número de dibujos por cuadro, incluidas imágenes, texto y gráficos vectoriales. Intente limitarlo a menos de 100.
WebGL
Lo siguiente representa el lote de envío de renderizado. Cada vez que se preparan los datos y se notifica a la GPU sobre el proceso de renderizado, se llama DrawCall. además de notificar a la GPU Además del lento proceso de renderizado
Cambiar materiales y sombreadores también es una operación que requiere mucho tiempo. El número de DrawCalls es un indicador importante que determina el rendimiento. Intente limitarlo a menos de 100.
Lienzo:
Tres valores: el número de lienzos redibujados por cuadro / el número de lienzos con el tipo de caché "normal" / el número de lienzos con el tipo de caché " bitmap" ".
CurMem: solo para renderizado WebGL, indica el uso de memoria y memoria de video (cuanto menor, mejor
Shader: solo para renderizado WebGL, indica el número de Envíos de sombreadores por fotograma.
Ya sea en modo Canvas o en modo WebGL, debemos centrarnos en los tres parámetros de DrawCall, Sprite y Canvas, y luego optimizarlos en consecuencia. (Consulte "Rendimiento de representación de gráficos")
Sección 3: Optimización de la memoria
Agrupación de objetos
La agrupación de objetos implica la reutilización continua de objetos. Una cierta cantidad de objetos se crea durante la inicialización de la aplicación y se almacena en un grupo. Cuando haya terminado de trabajar en un objeto, vuelva a colocar el objeto en el grupo donde podrá recuperarse cuando se necesite un nuevo objeto.
Debido a que la creación de instancias de objetos es costosa, el uso de un grupo de objetos para reutilizarlos reduce la necesidad de crear instancias de objetos. También puede reducir las posibilidades de que se ejecute el recolector de basura, aumentando así la velocidad del programa.
El siguiente código demuestra el uso de
Laya.utils.Pool:
1
2
3
p>4
5
6
7
8
9
10
11
12
ar SPRITE_SIGN = 'spriteSign'
var sprites = [ ];
función inicializar()
{
para (var i = 0; i lt; 1000; i )
{
var sp = Pool.getItemByClass(SPRITE_SIGN, Sprite)
sprites.push(sp);
Laya.stage.addChild(sp); p>
}
}
initialize();
Crea un grupo de objetos de tamaño 1000 en inicializar.
El siguiente código eliminará todos los objetos de visualización en la lista de visualización cuando se haga clic con el mouse y reutilizará estos objetos en otras tareas futuras:
1
2
3
4
5
6
7
8
9
10
Laya.stage.on("hacer clic", esto, función()
{ p>
var sp;
for(var i = 0, len = sprites.length; i lt; len; i)
{
sp = sprites.pop ();
Pool.recover(SPRITE_SIGN, sp);
Laya.stage.removeChild(sp); p>});
Después de llamar a Pool.recover, el objeto especificado se reciclará en el grupo.
Utilice Handler.create
Durante el proceso de desarrollo, Handler se utiliza a menudo para completar devoluciones de llamadas asincrónicas. Handler.create utiliza la gestión integrada del grupo de objetos, por lo que Handler.create debe usarse para crear controladores de devolución de llamada cuando se utilizan objetos Handler.
El siguiente código utiliza Handler.create para crear un controlador de devolución de llamada cargado:
1
Laya.loader.load(urls, Handler.create(this, onAssetLoaded));
En el código anterior, el grupo de objetos recuperará el controlador después de que se ejecute la devolución de llamada. En este punto, considere lo que sucederá con el siguiente código:
1
Laya.loader.load(urls, Handler.create(this, onAssetLoaded), Handler.create(this , onLoading) ));
En el código anterior, use el controlador devuelto por Handler.create para manejar el evento de progreso. La devolución de llamada en este momento es reciclada por el grupo de objetos después de ejecutarse una vez, por lo que el evento de progreso solo se activa una vez. En este momento, cuatro parámetros nombrados una vez deben establecerse en falso:
1
Laya.loader.load(urls, Handler.create(this, onAssetLoaded), Handler.create(this, onLoading, null, false));
Liberar memoria
JavaScript ejecutándose. No se puede iniciar el recolector de basura. Para garantizar que un objeto se pueda reciclar, elimine todas las referencias al objeto. La destrucción proporcionada por Sprite ayudará a establecer la referencia interna en nula.
Por ejemplo, el siguiente código garantiza que el objeto se pueda recolectar como basura:
1
2
var sp = new Sprite ();
sp.destroy();
Cuando un objeto se establece en nulo, no se eliminará de la memoria inmediatamente. El recolector de basura solo se ejecutará cuando el sistema crea que la memoria es lo suficientemente baja. La asignación de memoria (no la eliminación de objetos) desencadena la recolección de basura.
Basura
La recolección de basura puede ocupar una gran cantidad de CPU y afectar el rendimiento. Intente limitar el uso de la recolección de basura reutilizando objetos. Además, establezca las referencias en nulas siempre que sea posible para que el recolector de basura dedique menos tiempo a buscar el objeto
. A veces (por ejemplo, dos objetos se refieren entre sí), es imposible establecer ambas referencias en nulas al mismo tiempo. El recolector de basura escaneará los objetos inalcanzables y los borrará, lo que consumirá más rendimiento que el recuento de referencias.
Descarga de recursos
Muchos recursos siempre se cargarán cuando el juego se esté ejecutando. Estos recursos deben descargarse a tiempo después de su uso, de lo contrario permanecerán en la memoria.
El siguiente ejemplo muestra cómo comparar el estado del recurso antes y después de cargar el recurso:
1
2
3 p> p>
4
5
6
7
8
9 p>
10
11
12
13
14
15
16
17
18
var activos = [];
assets.push("res/apes/monkey0 .png ");
assets.push("res/apes/monkey1.png");
assets.push("res/apes/monkey2.png");
assets.push("res/apes/monkey3.png");
Laya.loader.load(assets, Handler.create(this, onAssetsLoaded));
función onAssetsLoaded()
{
for(var i = 0, len = activos.length; i lt; len; i)
{ p>
var activo = activos[i];
console.log(Laya.loader.getRes(activo));
Laya.loader.clearRes(activo );
console.log(Laya.loader.getRes(asset));
}
}
Acerca de filtros y máscaras.
Intenta minimizar el uso de efectos de filtro. Cuando se aplican filtros (BlurFilter y GlowFilter) a un objeto de visualización, se crean dos mapas de bits en la memoria en tiempo de ejecución. Cada mapa de bits tiene el mismo tamaño que el objeto de visualización. El primer mapa de bits se crea como una versión rasterizada del objeto de visualización y luego se usa para generar otro mapa de bits al que se aplica el filtro:
Dos mapas de bits en la memoria cuando se aplica el filtro
Cuando se modifica una propiedad del filtro o del objeto de visualización, ambos mapas de bits en la memoria se actualizarán para crear el mapa de bits resultante, y estos dos mapas de bits pueden ocupar una gran cantidad de memoria. Además, este proceso implica cálculos de CPU, lo que reducirá el rendimiento cuando se actualice dinámicamente (consulte "Rendimiento de representación de gráficos: acerca de cacheAs).
ColorFiter necesita calcular cada píxel en la representación de Canvas, mientras que en WebGL el consumo de GPU es insignificante.
La mejor práctica es utilizar mapas de bits creados por herramientas de creación de imágenes para simular filtros cuando sea posible. Evitar la creación de mapas de bits dinámicos en tiempo de ejecución puede ayudar a reducir el uso de CPU o GPU. y no se está modificando
Sección 4: Rendimiento de representación de gráficos
Optimización de Sprites
1 Minimiza el anidamiento jerárquico innecesario y reduce el número de Sprites. p>
2. Intente eliminar objetos en áreas no visibles de la lista de visualización o establezca visible=false
3. Si hay una gran cantidad de contenido estático o que. no cambia con frecuencia (como los botones), puede configurar el atributo cacheAs para todo el contenedor, lo que puede reducir en gran medida la cantidad de Sprites y mejorar significativamente el rendimiento.
Si hay contenido dinámico, es mejor separarlo del contenido estático para que solo se almacene en caché el contenido estático.
4. En el Panel, los subobjetos directos fuera del área del panel (los subobjetos de los subobjetos no se pueden juzgar) no se representarán. Los subobjetos más allá del área del panel no se consumirán.
Optimización de DrawCall
1. Configurar cacheAs para contenido estático complejo puede reducir en gran medida DrawCalls. Hacer un buen uso de cacheAs es la clave para la optimización del juego.
2. Intente asegurarse de que el orden de representación de las imágenes en el mismo atlas esté uno al lado del otro. Si se renderizan diferentes atlas, la cantidad de DrawCalls aumentará.
3. Intente asegurarse de que todos los recursos en el mismo panel utilicen un atlas, lo que puede reducir la cantidad de lotes de envío.
Optimización de Canvas
Al optimizar Canvas, debemos prestar atención a no utilizar caché. Como en las siguientes situaciones:
1. El objeto es muy simple, como por ejemplo. como palabra o imagen, configurar cacheAs=bitmap no solo no mejorará el rendimiento, sino que también provocará una pérdida de rendimiento.
2. Hay contenidos que cambian con frecuencia en el contenedor, como una animación o una cuenta regresiva en el contenedor. Si cacheAs=bitmap se establece en este contenedor, se perderá rendimiento.
Puedes determinar si la caché de Canvas se está actualizando observando el primer valor de las estadísticas de Canvas.
Acerca de cacheAs
Configuración
cacheAs puede almacenar en caché el objeto de visualización como una imagen estática. Cuando se utiliza cacheAs, si el subobjeto cambia, lo será. volver a almacenar en caché automáticamente. Al mismo tiempo, también puede llamar manualmente al método reCache para actualizar el caché.
Se recomienda almacenar en caché el contenido complejo que no cambia con frecuencia como imágenes estáticas, lo que puede mejorar en gran medida el rendimiento de la caché. Tiene tres valores opcionales: "ninguno", "normal" y "mapa de bits".
El valor predeterminado es "ninguno", que no realiza ningún almacenamiento en caché.
2. Cuando el valor es "normal", el almacenamiento en caché del lienzo se realiza en el lienzo y el almacenamiento en caché de comandos se realiza en el modo webgl.
3.
Cuando el valor es "mapa de bits", el caché del lienzo todavía se usa en el lienzo y el caché de renderTarget se usa en el modo webGL. Cabe señalar aquí que el modo de caché renderTarget en webGL tiene un límite de tamaño de 2048. Exceder 2048 aumentará la sobrecarga de memoria adicional. Además, la sobrecarga del redibujado continuo es relativamente alta, pero las llamadas a sorteos se reducirán y el rendimiento de renderizado será el más alto. El modo de caché de comandos en webGL solo reducirá el recorrido de nodos y la organización de comandos, pero no reducirá las llamadas a sorteos y el rendimiento es medio.
Después de configurar cacheAs, también puede configurar staticCache=true para evitar la actualización automática del caché y puede llamar manualmente al método reCache para actualizar el caché.
cacheAs mejora el rendimiento principalmente en dos aspectos. Uno es reducir el recorrido de nodos y el cálculo de vértices; el otro es reducir drawCall. Hacer un buen uso de los cachés será una herramienta poderosa para optimizar el rendimiento del motor.
El siguiente ejemplo dibuja 10.000 textos:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Laya.init(550, 400, Laya.WebGL
Laya.Stat.show()); ;
var textBox = new Laya.Sprite();
var text;
for (var i = 0; i lt; 10000; i )
{
texto = new Laya.Text();
text.text = (Math.random() * 100).toFixed(0);
text.color = "#CCCCCC";
text.x = Math.random() * 550
text.y = Math.random(); * 400;
textBox.addChild(texto);
}
Laya.stage.addChild(textBox); En las capturas de pantalla en tiempo de ejecución tomadas en mi computadora, el FPS es estable en alrededor de 52.
Cuando configuramos el contenedor donde se encuentra el texto en caché, como se muestra en el siguiente ejemplo, el rendimiento mejora enormemente y el FPS alcanza los 60 fotogramas.
1
2
// ...Omitir otro código... var textBox = new Laya.Sprite()
textBox.cacheAs = "bitmap"; // ...Omitir otro código...
Trazo de texto
En tiempo de ejecución, el texto con un trazo llama al comando de dibujo una vez más que el texto sin trazo. En este momento, el uso de texto por parte de la CPU es proporcional a la cantidad de texto. Por lo tanto, intente utilizar alternativas para satisfacer las mismas necesidades.
Para contenido de texto que apenas cambia, puede utilizar cacheAs para reducir el consumo de rendimiento. Consulte "Rendimiento de representación de gráficos: acerca de cacheAs".
Para campos de texto cuyo contenido cambia con frecuencia pero utiliza una pequeña cantidad de caracteres, puede optar por utilizar fuentes de mapa de bits.
Omitir el formato del texto y renderizarlo directamente
En la mayoría de los casos, muchos textos no requieren un formato complejo y simplemente muestran una línea de texto. Para satisfacer esta demanda, Text proporciona un método llamado changeText que puede omitir directamente la composición tipográfica.
1
2
3
4
5
var texto = new Text();
text.text = "text";
Laya.stage.addChild(text);
//Simplemente actualice el contenido del texto más adelante, usar changeText puede mejorar el rendimiento
text.changeText("text change.");
Text.changeText modificará directamente la última instrucción para dibujar el texto en la instrucción de dibujo. Este comportamiento del comando de dibujo anterior que aún existe hará que changeText se use solo en las siguientes situaciones:
El texto siempre tendrá una sola línea.
El estilo del texto es siempre el mismo (color, peso, cursiva, alineación, etc.).
Aun así, estas necesidades todavía se utilizan a menudo en la programación real.
Sección 5: Reducir el uso de CPU
Reducir la búsqueda de propiedades dinámicas
Cualquier objeto en JavaScript es dinámico y puede agregar propiedades a voluntad. Sin embargo, buscar un atributo entre una gran cantidad de atributos puede llevar mucho tiempo. Si necesita utilizar un determinado valor de atributo con frecuencia, puede utilizar variables locales para guardarlo:
1
2
3
4
5
6
7
8
función foo()
{
var prop = target.prop;
// Usar prop
proceso1(prop);
proceso2(prop); );
process3(prop);
}
Temporizador
LayaAir proporciona dos bucles de temporizador para ejecutar bloques de código.
La frecuencia de ejecución de Laya.timer.frameLoop depende de la frecuencia de fotogramas. La velocidad de fotogramas actual se puede ver a través de Stat.FPS.
La frecuencia de ejecución de Laya.timer.loop depende del tiempo especificado por el parámetro.
Cuando finalice el ciclo de vida de un objeto, recuerda borrar su Timer interno:
1
2
3
4
5
6
Laya.timer.frameLoop(1, esto, animateFrameRateBased);
Laya. stage.on("hacer clic", esto, disponer);
función disponer()
{
Laya.timer.clear(esto, animateFrameRateBased);
}
Cómo obtener el límite del objeto de visualización
En el diseño relativo, a menudo es necesario obtener el límite del objeto de visualización correctamente. Hay muchas formas de obtener los límites del objeto mostrado y es necesario conocer las diferencias.
1. Utilice getBounds/getGraphicBounds.
,
1
2
3
4
var sp = nuevo Sprite(); p> p>
sp.graphics.drawRect(0, 0, 100, 100, "#FF0000");
var límites = sp.getGraphicBounds();
Laya.stage.addChild(sp);
getBounds puede satisfacer la mayoría de las necesidades, pero debido a que necesita calcular límites, no es adecuado para llamadas frecuentes.
2. Establezca el tamaño automático del contenedor en verdadero.
1
2
3
4
var sp = nuevo Sprite();
sp.autoSize = true;
sp.graphics.drawRect(0, 0, 100, 100, "#FF0000");
Laya.stage.addChild( sp);
El código anterior puede obtener correctamente el ancho y el alto en tiempo de ejecución. autoSize se volverá a calcular cuando se obtengan el ancho y el alto y cambie el estado de la lista de visualización (autoSize calcula el ancho y el alto a través de getBoudns). Por lo tanto, no es recomendable aplicar autoSize a un contenedor con una gran cantidad de subobjetos. Si se establece el tamaño, el tamaño automático no tendrá efecto.
Obtener el ancho y alto después de usar loadImage:
1
2
3
4 p>
5
6
var sp = nuevo Sprite();
sp.loadImage("res/apes/monkey2.png", 0, 0, 0, 0, Handler.create(this, function()
{
console.log(sp.width, sp.height);
}));
Laya.stage.addChild(sp);
loadImage puede obtener el ancho y el alto correctamente solo después de que se activa la función de devolución de llamada después de la carga.
3. Llame directamente a la configuración de tamaño:
1
2
3
4
5
6
7
8
Laya.loader.load("res/apes/monkey2.png " , Handler.create(this, function()
{
var textura = Laya.loader.getRes("res/apes/monkey2.png");
var sp = new Sprite();
sp.graphics.drawTexture(textura, 0, 0);
sp.size(textura.ancho, textura.alto) ;
Laya.stage.addChild(sp);
}));
El uso de Graphics.drawTexture no establecerá automáticamente el ancho y el alto del contenedor. , pero puede usar el ancho y el alto de la textura para darle al contenedor. No hace falta decir que esta es la forma más eficaz.
Nota: getGraphicsBounds se utiliza para obtener el ancho y alto del dibujo vectorial.
Cambie la velocidad de fotogramas según el estado de la actividad
Hay tres modos de velocidad de fotogramas, Stage.FRAME_SLOW mantiene FPS en 30; Stage.FRAME_FAST mantiene FPS en 60; mantiene selectivamente FPS a 30 o 60 fotogramas.
A veces no es necesario ejecutar el juego a 60 FPS, porque 30 FPS ya pueden satisfacer la respuesta de la visión humana en la mayoría de los casos. Sin embargo, 30 FPS pueden causar incoherencia en la pantalla durante la interacción del mouse, por lo que Stage.FRAME_MOUSE. Llegó a existir.
El siguiente ejemplo muestra cómo mover el ratón sobre el lienzo a la velocidad de fotogramas de Stage.FRAME_SLOW para que la bola siga el movimiento del ratón:
1
2
3
4
5
6
7
8
9
10
11
12
Laya.init(Browser.width, Browser .height);
Stat.show();
Laya.stage.frameRate = Stage.FRAME_SLOW;
var sp = new Sprite();
sp .graphics.drawCircle(0, 0, 20, "#990000");
Laya.stage.addChild(sp);
Laya.stage .on(Event.MOUSE_MOVE, this, function()
{
sp.pos(Laya.stage.mouseX, Laya.stage.mouseY);
});
En este momento, el FPS se muestra como 30 y cuando se mueve el mouse, puede sentir que la actualización de la posición de la pelota es inconsistente. Establezca Stage.frameRate en Stage.FRAME_MOUSE:
1
Laya.stage.frameRate = Stage.FRAME_MOUSE;
En este momento, se mostrarán FPS después el mouse se mueve 60 y se mejora la suavidad de la imagen. Después de que el mouse permanezca inmóvil durante 2 segundos, FPS volverá a 30 fotogramas.
Usar callLater
callLater retrasa la ejecución del bloque de código hasta antes de renderizar este fotograma. Si la operación actual cambia con frecuencia el estado de un objeto, puede considerar usar callLater para reducir los cálculos repetidos.
Considere un gráfico en el que establecer cualquier propiedad que cambie la apariencia hará que se vuelva a dibujar el gráfico:
1
2
3
4
5
6
7
8
9
9
p>
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 p>
var rotación = 0,
escala = 1,
posición = 0
función setRotation(valor)
{
this.rotation = valor ;
actualizar();
}
función setScale(valor)
{
this.scale = valor;
actualizar();
}
función setPosition(valor) p>
{
esta .posición = valor;
actualización();
}
función actualización()
{
console.log('rotation: ' this.rotation '\tscale: ' this.scale '\tposition: ' posición);
}
Llame al siguiente código para cambiar el estado:
}
Llame al siguiente código para cambiar el estado:
p>1
setRotation(90); setScale(2); setPosition(30);
El resultado impreso de la consola es
rotación: 90 escala: 1 posición: 0
rotación: 90 escala: 2 posición: 0
rotación: 90 escala: 2 posición: 30
la actualización se llama tres veces y el resultado final es correcto, pero las dos primeras llamadas son innecesarias.
Intenta cambiar las tres actualizaciones a:
1
Laya.timer.callLater(this, update);
En este vez, la actualización solo se llamará una vez y es el resultado que queremos.
Carga de imágenes/atlas
Después de completar la carga de imágenes/atlas, el motor comenzará a procesar recursos de imágenes. Si se carga una galería de imágenes, se procesará cada subimagen. Si se procesa una gran cantidad de imágenes a la vez, este proceso puede provocar grandes retrasos.
En la carga de recursos del juego se pueden cargar recursos según niveles, escenarios, etc. Cuantas menos imágenes tengas que procesar al mismo tiempo, más responsivo será el juego en ese momento. Una vez utilizado el recurso, también se puede descargar para liberar memoria.
Sección 6: Otras estrategias de optimización
Reduzca la cantidad de partículas utilizadas y trate de no usar partículas en el modo Canvas en la plataforma móvil.
2. En el modo Canvas, intente reducir el uso de rotación, escala, alfa y otros atributos, que consumirán rendimiento. (Se puede utilizar en modo WebGL);
3. No cree objetos ni cálculos complejos en el bucle de tiempo
4. uso de getBounds() Úselo, porque estas llamadas generarán más cálculos
5. Intente usar try catch lo menos posible, la ejecución de la función capturada por try será muy lenta; >