Elementos básicos con Pixi.js: Primitivas y Sprites
Creación de primitivas
Las primitivas son formas geométricas básicas que podemos dibujar directamente mediante instrucciones. En Pixi.js las instrucciones utilizadas para crear estos gráficos son muy similares (pero no iguales) a las que se utilizan para dibjuar en un elemento HTML Canvas mediante Javascript puro.
Preparación del escenario
Lo primero será crear una aplicación PIXI tal como en la sección anterior, pero con algunos cambios menores:
// the size of the stage, as variables
let stageWidth = 480;
let stageHeight = 240;
// create app
let app = new PIXI.Application({
width: stageWidth,
height: stageHeight,
antialias: true,
backgroundColor: 0xEEEEEE
});
// add canvas to HTML document
document.body.appendChild(app.view);
Los únicos cambios son la adición de un parametro más en la función Aplication
, llamado antialias
, el cual mejora la visualización de los bordes de los elementos en pantalla.
Además ahora el ancho y el alto del escenario son declarados como variables, para que esos valores puedan ser reutilizados en distintas partes de nuestro código.
Un primer círculo
Para crear un gráfico llamado myCircle
usamos el constructor Graphics, que permite dibujar líneas, círculos, rectángulos, polígonos, entre otras formas. Así obtenemos un objeto en el que podremos dibujar además de manipular libremente, cambiando sus propiedades.
// draw a circle
let myCircle = new PIXI.Graphics();
Para hacer nuestro círculo utilizamos una secuencia de 5 instrucciones:
myCircle.lineStyle(2, 0x993333);
myCircle.beginFill(0xCC3333);
// params: pos x, pos y, radius
myCircle.drawCircle(100, 100, 25);
myCircle.endFill();
Y cada una de esas líneas tienen esta misión:
- con
lineStyle
asignamos el estilo de la línea, de grosor 2 pixeles y color de borde0x993333
- con
beginFill
comenzamos a rellenar la forma geométrica, con el color0xCC3333
- con
drawCircle
dibujamos el círculo propiamente dicho, ingresando las coordenadasx
yy
donde se ubicará el centro del círculo, seguidas del radio deseado, en pixeles. - con
endFill
terminamos el proceso de rellenado
Esos son todos los pasos requeridos para dibujar nuestro círculo. Sin embargo, el proceso de dibujo se ha llevado a cabo dentro del gráfico myCircle
, que es una variable. Es decir todo el tiempo hemos estado dibujando en la memoria del computador. Hace falta un paso más para poder ver nuestro círulo en pantalla.
Poner elementos en el escenario
El paso final consiste en llamar la función addChild
del escenario de nuestra aplicación, lo cual hará que nuestro gráfico myCircle
quede visualmente incorporado también en pantalla y no solamente en la memoria:
app.stage.addChild(myRect);
Así las cosas, el código completo necesario para dibujar un círculo y mostrarlo en pantalla es el siguiente:
let myCircle = new PIXI.Graphics();
myCircle.lineStyle(2, 0x993333);
myCircle.beginFill(0xCC3333);
myCircle.drawCircle(240, 120, 40);
myCircle.endFill();
app.stage.addChild(myCircle);
y el resultado es un círculo con radio de 40 pixeles y ubicado en el centro del escenario:
Debe tenerse en cuenta que las coordenadas del objeto myCircle
en el escenario siguen siendo (0, 0) y que el círculo dibujado dentro de ese objeto es el que está desplazado hasta las coordenadas (240, 120). Esto podría ser problemático en algunos casos y por esa razón exploraremos más este tema en la sección dedicada a contenedores.
Qué tal un rectángulo?
Siguiendo un procedimiento similar, podemos crear e insertar un rectángulo amarillo, pero esta vez en el origen de escenario (0, 0), es decir la esquina superior izquierda:
let myRect = new PIXI.Graphics();
myRect.lineStyle(4, 0xEEBB00);
myRect.drawRect(0, 0, 48, 48); // x, y, width, height
app.stage.addChild(myRect);
Cambiando las propiedades del gráfico
El grosor del borde puede afectar la posición y el tamaño exacto de un elemento. Puede observarse que, a pesar de haberse creado en el punto (0, 0), parte del borde está fuera del espacio visible. Esto se debe a la forma en que las instrucciones dibujan los bordes de las figuras. Este comportamiento, por supuesto, es configurable y podremos modificarlo más adelante.
Después de haber agregado el gráfico en el escenario, haremos una manipulación de las propiedades del rectángulo, llevándolo al centro del escenario y cambiando sus dimensiones originales para que ahora mida el doble, es decir 96 pixeles de cada lado:
myRect.width = 96;
myRect.height = 96;
myRect.x = (stageWidth - myRect.width) / 2;
myRect.y = (stageHeight - myRect.height) / 2;
Con lo cual obtenemos el siguiente resultado:
Crear texto
La forma más rápida de crear texto es similar:
let myText = new PIXI.Text('Morning Coffee!')
app.stage.addChild(tagline);
Sin embargo, el texto creado tendrá un estilo (fuente, color, peso, etc.) por defecto. Para mejorar la apariencia de nuestro texto, es necesario crear un objeto de estilo de texto, que nos permita controlar cada característica:
let textStyle = new PIXI.TextStyle({
fill: '#DD3366',
fontFamily: 'Open Sans',
fontWeight: 300,
fontSize: 14
});
Entonces asignando el estilo a nuestro elemento de texto, mostraremos en pantalla un mensaje mucho más personalizado. Lo ubicaremos en el centro de la pantalla, usando también la propiedad anchor
, que nos permite controlar el punto de anclaje del elemento, para que sirva como referencia a la hora de ubicarlo:
let myText = new PIXI.Text('Morning Coffee!', textStyle) // <-
myText.anchor.set(0.5);
myText.x = 240;
myText.y = 120;
app.stage.addChild(myText);
De lo que obtenemos:
A continuación una versión en vivo donde se combinan todos los elementos básicos:
Agregando Sprites
Los Sprites son elementos visuales en 2D que pueden insertarse dentro del escenario de cualquier ambiente gráfico de aplicaciones interactivas o videojuegos. Son los recursos gráficos más simples quepodemos poner en pantalla y controlar desde el código de nuestra aplicación, mediante la manipulación de propiedades como sus dimensiones, rotación o poscición, entre otras.
En general los sprites se crean a partir de imágenes o mapas de bits. La forma más fácil, aunque no necesariamente la mejor en todos los casos, es crearla directamente de un archivo:
let coffee = new PIXI.Sprite.from('images/coffee-cup.png');
app.stage.addChild(coffee);
Tras lo cual veríamos lo siguiente:
Aunque este método es simple, es inconveniente si el archivo de imagen es grande, pues la carga demorará más de lo esperado y las instrucciones siguientes relacionadas con el sprite podrían tener resultados inesperados.
Sprites mediante la carga de texturas
La mejor forma de cargar uno o varios recursos externos es mediante el uso de la clase Loader
ofrecida por Pixi.js. Para nuestra conveniencia, el objeto PIXI
ofrece una instancia del cargador pre-fabricada que se puede usar sin mayor configuración.
const loader = PIXI.Loader.shared;
Después de la instanciación de esta utilidad, podemos cargar el mismo archivo pero con el nuevo método:
let myCoffee; // it will store the sprite
loader
.add('coffee', 'images/coffee-cup.png')
.load((loader, resources) => {
// this callback function is optional
// it is called once all resources have loaded.
// similar to onComplete, but triggered after
console.log('All elements loaded!');
})
.use((resource, next) => {
// middleware to process each resource
console.log('resource' + resource.name + ' loaded');
myCoffee = new PIXI.Sprite(resource.texture);
app.stage.addChild(myCoffee);
next(); // <- mandatory
})
En el código anterior utilizamos la función add
para agregar a la cola los elementos que deseamos cargar, con un nombre que le queremos asignar (en este caso coffee), además de la ruta al archivo de imagen.
Podemos encadenar las funciones load
y use
para hacer tareas con los elementos cargados. La primera se ejecuta cuando la carga de todos los elementos se ha completado. La segunda funciona como un middleware después de que cada elemento ha sido cargado.
Sin embargo, el verdadero poder de la clase Loader
se hace evidente cuando queremos cargar múltiples archivos al mismo tiempo. Para esto necesitaremos un objeto, que llamaremos sprites
, que pueda alojar a todos los elementos que serán cargados, en lugar de tener una variable para cada uno de ellos.
let sprites = {};
let xpos = 16;
loader
.add('coffee', 'images/coffee-cup.png')
.add('muffin', 'images/muffin.png')
.add('icecream', 'images/ice-cream.png')
.add('croissant', 'images/lollipop.png')
.use((resource, next) => {
// create new sprite from loaded resource
sprites[resource.name] = new PIXI.Sprite(resource.texture);
// set in a different position
sprites[resource.name].y = 16;
sprites[resource.name].x = xpos;
// add the sprite to the stage
app.stage.addChild(sprites[resource.name]);
// increment the position for the next sprite
xpos += 72;
next(); // <- mandatory
})
Recordemos que use
se ejecuta varias veces, una por cada elemento agregado a la cola de carga. Esto dará como resultado lo siguiente:
Pero además, la instancia loader
envía diversas señales durante del proceso de carga, que podemos aprovechar para obtener información adicional sobre cómo avanza la carga. El código siguiente mostraría mensajes en la consola:
loader.onProgress.add((loader, resource) => {
// called once for each file
console.log('progress: ' + loader.progress + '%');
});
loader.onError.add((message, loader, resource) => {
// called once for each file, if error
console.log('Error: ' + resource.name + ' ' + message);
});
loader.onLoad.add((loader, resource) => {
// called once per loaded file
console.log(resource.name + ' loaded');
});
loader.onComplete.add((loader, resources) => {
// called once all queued resources has been loaded
// triggered before load method callback
console.log('loading complete!');
});
Míralo en vivo aquí:
Top comments (1)
Buena serie sobre todo para aquellos que inician con PIXI.js como yo. Gracias al autor.