miércoles, 3 de febrero de 2021

Simple RayCast 2D - javascript

Permite mostrar gráficos estilo Wolfenstein de los 80s, con el pequeño plus (o des, según se vea) de darle relevancia al piso y esconder el techo.

Puedes descarga el script desde la siguiente link Download

Instalación

Debe incluirse la clase dentro el cuerpo del html. Para usarse al insarse al instante debe ir dentro de la etiqueta <head> para cargarse antes del body. De lo contrario, puede cargarse en el body, y debe usarse hasta confirmar que ha cargado.

<script type="simple_raycast2D.js"></script>

Inicializar el motor

Para comenzar debemos hacer una instancia de simple_raycast2D

// inicia el motor gráfico RayCast2D
rc = new simple_raycast2D(
    canvas, // elemento html canvas
    maxx, // profundidad del rayo casteador
);

El primer argument es un elemento htm canvas, que es posible crearlo en el instante o tomar uno que ya se encuentre en el body. El segundo argumento es la profundidad de campo o área visible.

Por ejemplo:

rc = new simple_raycast2D(
    canvas = document.createElement('canvas')
    maxx = 16
);

Tamaño del canvas

El motor no usa la tarjeta gráfica, en cambio usa el procesador. Por esta razón el tamaño de dibujo del canvas debe ser limitado a usar baja resolución para que corra bien.

A parte, sabemos que se le puede dar un tamaño de impresión independiente al tamaño de dibujo del canvas. La función calcula automáticamente el ancho de dibujo del canvas.

Esto se especifica con el siguiente estilo y la siguiente función

canvas {
    width: 100vw;
    height: 100vh;
    position: fixed;
    left: 0;
    right: 0;
}

rc.resizeTo(scr_height = 90);

donde src_height especifica la altura del canvas. Recomiendo que sea entre 80 y 160 pixeles. 

Cada que la ventana cambie de tamaño o la orientación, debería llamarse.

window.addEventListener('resize',function(){
	rc.resizeTo(90);
});
rc.resizeTo(90);

El mapa

Para especificar un mapa debemos definir dos variables, tiles y mapa. Donde tiles es un arreglo de bits correspondiente a la imagen que se usara como tile map y puede extraerse usando la función imData. Y mapa en un arreglo bidimencional de 16bits.

rc.tiles = imData('mapTexture.png',true);
rc.mapa = [
    [0,0,0,0],
    [0,0,0,0],
    [0,0,0,0]
];

Por ejemplo, el array para el mapa es de 4x3. Y donde se esperan números de 16 bits.

  • El bit 1 indica si es pared.
  • El bit 2 indica si debe comportarse como diagonal,
  • Los bit 3,4,5,6,7,8 indican el id del sprite 0-63 para la pared,
  • Los bit 9,10,11,12,13,14 indican el id del sprite 0-63 para el piso, 
  • Los bit 15,16 indican como dibujar el piso:
    • 0 - normal,
    • 1 - rotación 90°,
    • 2 - rotación 180°,
    • 3 - rotación -90°


En seguida un ejemplo de cómo usar mapas separados

mapa_es_pared = [
    [1,0,0,1],
    [1,0,0,1],
    [1,1,1,1]
];
mapa_sprites_pared = [
    [5,0,0,5],
    [4,0,0,4],
    [5,6,5,5]
];
mapa_sprites_piso = [
    [0,4,6,0],
    [0,7,5,0],
    [0,0,0,0]
];
mapa_piso_rotacion = [
    [0,0,1,0],
    [0,3,2,0],
    [0,0,0,0]
];
mapa = [];
for(var y=0; y<3; y++){
    mapa[y] = [];
    for(var x=0; x<4; x++)
        mapa[y][x] = mapa_es_pared[y][x] +
            (mapa_sprites_pared[y][x] << 2) +
            (mapa_sprites_piso[y][x] << 8) +
            (mapa_piso_rotacion[y][x] << 14);
}
rc.mapa = mapa;

La camara básica

Para posicionar la cámara se puede usar el siguiente comando

rc.cam({x: 1, y: 1, a:0});

donde "xy" es la posición de la cámara dentro del mapa, y "a" es el ángulo en radianes que la cámara estará mirando.

Los objetos

Para agregar objetos a la escena, primero debes crear las texturas de las cuales se tomaran los gráficos. Eso se hace con el siguiente comando.

var sprite_texture = rc.textura(imData('sprite.png'), width, height);

La textura se carga asincronamente, de modo que no necesitas esperar a que cargue para poderla usar.

La textura debe contener los sprites del objeto organizados horizontalmente por cara y verticalmente por animación. Es decir, cada columna de sprites dentro de la imagen corresponde a un ángulo o cara del objeto, y cada renglón corresponde a un cuadro de animación.

Ahora, ya podemos crear un objeto con el siguiente comando

obj_n = rc.crear(
    sprite_texture, // indica la textura que usará el objeto
    angulos, // indica la cantidad de caras del objeto     angulos, // indica la cantidad de caras del objeto     divY, // indica la cantidad de cuadros del objeto     ancho, // indica el ancho con el que será dibujado el objeto     animacion, // es un array que indica el orden de los frames     inverso,     sorteable );

Para el array que indica el orden de frames de animación, en el caso deuna imagen de 4x3 sprites sería [0,4,8], pensando en que usa 4 caras, y una animación de 3 cuadros.

Posicionar y animación de objetos

Para indicar la posición de un objeto usa el siguiente comando

rc.pos(obj_n, {
    x: 2,    // posición x
    y: 1.5,    // posición y
    z: 0,    // posición altura
    a: 0,    // angulo
    v: true    // visible
});

El primer argumento es el índice devuelto por la función crear. El segundo atributo es un objeto con cualidades de la entidad, donde ningún atributo del objeto es obligatorio.

También puedes indicar una animación con el siguiente comando. Donde el primer argumento es el índice devuelto por la función crear. El segundo argumento es un objeto con la información de la animación.

rc.anim(obj_n, {
    c: 0, // indica el cuadro donde comienza la animación
    l: 3, // indica la duración de la animación
    r: 2, // indica cuántos cuadros rebobinará al terminar la animación
    s: 0.1, // indica la velocidad de la aniamción
});

por ejemplo,  en la imagen tenemos en el primer cuadro de pie, y en los siguientes dos la animación de caminar. Para ponerle a caminar, comenzamos en el cuadro 0, donde está de pie, y especificamos una duración de 3 cuadros, que son uno de pie y dos de caminado, y por último 2 cuadros de retroceso al terminar, para que se repitan los cuadros de caminado.

Loop Update

Para terminar, debemos llamar la función dibujar a cada instante para actualizar y redibujar

rc.dibujar(showWalls, showFloor);

donde el primer argumento es un valor booleano para indicar si debe dibujar las paredes, y el segundo indica si debe dibujar el piso.

DOCUMENTACION

/*

// inicia el motor gráfico RayCast2D
rc = new simple_raycast2D(
    canvas, // elemento html canvas
    maxx, // profundidad del rayo casteador
);

// para cargar especificar la textura del piso y las paredes
rc.tiles = imData('mapTexture.png',true);

// además se puede cargar una imagen escala de grises de protuberancia para el piso
rc.tilesDepth = imData('depthTexture.png',true);

// para cargar el mapa, se hace con un array bidimencional de W x H,
// donde se esperan numeros de 16bits. El bit(1) indica si es pared, el bit(2) indica si
// debe comportarse como diagonal, el bit(3,4,5,6,7,8) es el id del sprite 0-63 para la pared,
// el bit (9,10,11,12,13,14) indica el id del sprite 0-63 para el piso,  el bit(15,16) indican
// como dibujar el piso: 0 nada, 1 rotación 90°, 2 rotación 180°, 3 rotación -90°
rc.mapa = [[0]];

// posiciona la camara opc = {x, y, a}
rc.cam(opc);

// posiciona la camara aditivamente opc = {x, y, a}
rc.camMov(opc);

// posiciona la camara automáticamente con forme al mapa sobre el objecto n. A partir de
// la posición actual de la camara voltea a ver al objeto n y lo rodea "ang" grados,
// acercandose o alejandose a "z" disancia
rc.camAuto(n, ang, z);

// posiciona la camara en alineación camara, n, m, a una distancia z de n
rc.camAtack(n,m,z);

// carga una textura. "width, height" es el acho y
// alto en pixeles de la imagen. Es necesario proporcionarlos para la carga ascincrona
// si se precarga, basta con "width" 
rc.textura(imagenData, width, height);

// se encarga de actualizar el canvas. "showWalls" es un booleano que especifica si debe mostrar
// la paredm "showFloor" es un booleano que especifica si debe mostrar el piso, "es3D" es un booleano
// que especifica si dibujará lateralmente otra camará para efecto 3D, "_x,_y" especifica la posición en
// el canvas donde comenzará a dibujar, __paletteChange es un objecto que pretende cambia run color por
// otro, por ejemplo {0:20} (el 12 por el 20), pero tambien puede ser una sucición de colres {0:[20, 21, 15]}
rc.dibujar(showWalls, showFloor, false, false, false, es3D, _x, _y, __paletteChange);

// Crea un objecto con la textura "textura" create previamente, misma que está dividida en "angulos"
// o columnas que representan las caras del objeto, y dividida en "divY" renglones de animación, presentando en pantalla con un "ancho" factor
// correspondiente a el tamaño de los bloques, "animación" si es definido debe ser un array con el nuevo
// orden de los renglones de animación, "inverso" es un voleano que indica si las caras están en orden
// inverso, "sorteable" es un boleano que especifica si se dibujará en orden de distancia
rc.crear(textura, angulos, angulos, divY,ancho, animacion, inverso, sorteable);

// elimina el objeto "n"
rc.borrar(n){ this.obj_activo[n] = false; ;

// posiciona el objecto "n" { x:pos,y:pos,z:pos, a:angulo, v:boolean_visible }
rc.pos(n, opc);

// posiciona aditivamente el objecto "n" { x:pos,y:pos,z:pos, a:angulo, v:boolean_visible }
rc.mov(n, opc);

// anima un objeto "n", comienza en el renglon "opc.c" y avanza "opc.l" renglones, entonces retrocede
// "opc.r" renglones para realizar un loop
rc.anim(n, opc);

// crea un objecto emporal tomado el objeto "n" y lo anima desde el renglon "opc.c" hasta "opc.l"
// renglones, en la posición "opc.x, opc.y" cuando termina se destruye amenos que "isTemp" sea
// definido igual a false
rc.animX(n, opc, isTemp);

// devuelve los objectos que se encuentran en el arco de posición "x,y", de angulo "a", de arco "arc"
// a una distancia máxima "max"
rc.getObjectsByRayCast(x,y,a,arc_size,max);

// devuelve los objetos que tocan una barra que inicia en "x,y", de angulo "a", un grosor "w" y
// un largo "max"
rc.getObjectsByRayCastAndBar(x,y,a,w,max);

// devuelve la imagen de ruta "url", donde "isBlocks" es booleano  que especifica si servirá para
// paredes y techo (true - que pondra la secuencia por cuadro), o para objetos (false - que no
// pondra secuencia).
imageData = imData(url,isBlocks);

// actualiza el tamaño de la pantalla al tamaño html del canvas, donde scr_height es el alto en piexeles
// por default 90
rc.resizeTo(scr_height);
*/