Mostrando las entradas con la etiqueta Videojuego. Mostrar todas las entradas
Mostrando las entradas con la etiqueta Videojuego. Mostrar todas las entradas

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);
*/

jueves, 9 de noviembre de 2017

WebGl BufferGeometry Sprite

Éste pretende ser un motor de animación 3d usando cuadros, basado en THREE.js.

... el ejemplo lo tendré pronto ...

*** Download ***

https://drive.google.com/file/d/1hHsDfnmpmjUOaF87CF3IRwi6A84UaSjt/view?usp=sharing

*** Documentación ***

Cargar textura
```
var textureLoader = new THREE.TextureLoader();
textureLoader.load('photo.png', function (texture){
... código ...
});
```

Iniciar motor
Se le pasa la escena three js
```
var enSp = new WebGl_BufferGeometry_Sprite(scene);
```

Crear grupo o familia de textura
Para aumentar la velocidad del plotéo, se me ocurre que todos los objetos tridimencionales que compartan una textura, serán el mismo objeto THREE.BufferGeometry y manipularé sus vertices para que funjan como objetos independientes.
```
var groupMesh = enSp.addTexture(texture);
```

Agregar un objeto 3d
Se le debe pasar el arreglo con las vertices del objeto, el arreglo del mapa de textura, el arreglo de los indices de los polígonos,  arreglo de vertices agrupadas,  arreglo de cuadros de animación.
```
vtx = [[0,2,0,2,-1,0,-2,-1,0], [0,2.2,0,2,-1,0,-2.2,-1,0]];
uvmap = [0.5,0,1,1,0,1];
index = [0,1,2];
groups = [[{x:0,y:0,z:0},2,1],[..  ..],..  ..];
animation = [0,1,0];
var sprite = enSp.addSprite(groupMesh, vtx, uvmap, index, groups, animation);
```

  • Array con las vertices del objeto. Es un array unidimencional que contiene los componentes xyz de las vertices uno tras otro. Además, para agregar cuadros de animación, es posible pasarle un array de arrays donde cada sub array es un nuevo array unidimencional que contiene los componentes xyz de cada cuadro. Importante. Se respeta el orden de las vertices, esperando que la diferencia de entre ellas sea la animación.
  • Array del mapa de textura. Es un array unidimencional que contiene los componentes uv de cada vértice. Importante. nótese que es por vértice y no por polígono. de modo que si queremos tener dos polígonos vecinos con distinto mapa de textura, se requerirá que cada uno tenga sus vertices particulares. Tratando de evitar el caso anterior porque al aumentar las vertices, se reduce el desempeño.
  • Array de indices de polígonos. Es un array unidimencional que contiene los componentes abc de cada polígono, uno tras de otro.
  • Array de vertices agrupadas. Es un array bidimencional que en sus subarreglos contiene un objeto {x,y,z} en su primera posición que especifica el origen del grupo, y seguido, el indice de las vertices que conforman el grupo. Esto es útil para animar o mover por separado un grupo de un objeto.
  • Array de cuadros de animación. Es un array bidimencional que contiene el orden en que el indice de los cuadros de animación se sucederán (los cuadros son los subarrays del Array de vertices). Por ejemplo, si tengo una animación de maniar en tres cuadros, y deseo que se repitan en loop, usaría 0,1,2,1  ... de modo que al animar se sucederían 012101210 ...

Un sprite Objeto o sprite, tiene funciones que permiten modificar su posición, rotación, etc.

  • remove. Remueve el sprite u objeto del motor.
    ```
    sprite.remove();
    ```
  • pos. Especifica la posición de un sprite pasándole un elemento del tipo {x,y,z} y un valor booleano positivo si queremos que sea aditivo en vez de imperativo.
    ```
    sprite.pos({x:10, y:1, z:0,1}, false);
    ```
  • ang. Especifica el ángulo de un sprite pasándole un elemento del tipo {x,y,z} y un valor booleano positivo si queremos que sea aditivo en vez de imperativo. Usa radianes.
    ```
    sprite.ang({x:10, y:1, z:0,1}, false);
    ```
  • scale. Especifica la escala de un sprite pasándole un elemento del tipo {x,y,z} y un valor booleano positivo si queremos que sea aditivo en vez de imperativo. Por default cada uno valen 1. Además es posible especificar todos de un solo golpe pasándole un número en vez del objeto xyz.
    ```
    sprite.scale({x:2, y:2, z:2}, false);
    sprite.scale(2, false);
    ```
  • scroll. Permite recorrer la textura de un objeto o sprite pasándole la cantidad que se moverá en uv el mapa de las vertices.
    ```
    sprite.scroll(u,v);
    ```
  • anim. Anima un sprite según los siguientes parámetros (solo el cuadro inicial es obligatorio -start-):
    ```
    sprite.anim(start, stopin, back, step, onsuccess,  targetx, targety, targetz, targeteasefn, onstep);
    ```
    • cuadro inicial. cuadro con el que arranca la animación.
    • cuadro final. cuadro donde se detendrá la animación.
    • retroceso. si se especifica, volverá el puntero de animación la cantidad `retroceso` permitiendo con ello hacer un loop.
    • velocidad. es la velocidad del puntero en cambiar de cuadro. Por default es 1, pero puedes hacer que dure el doble pasándolo a 0.5
    • animación success. Es una función que se ejecuta cada que la animación supera el cuadro final, o bien, si se reinicia el loop.
    • target x. Especifica si se debe trasladar a algún punto mientras dura la animación.
    • target y. Especifica si se debe trasladar a algún punto mientras dura la animación.
    • target z. Especifica si se debe trasladar a algún punto mientras dura la animación.
    • target ease. si se especifica un punto de traslado, es la función ease que usara para realizar el traslado. Por default es lineal. 
    • función onstep. Es ta función se ejecuta cada que el puntero de la animación avanza, siendo sus argumentos:
      • tEase. un numero entre 0 y 1 que refleja el avance de la animación de forma ease.
      • t. un numero entre 0 y 1 que refleja el avance de la animación de forma real.
  • setGroup. permite cambiar la posición, ángulo o escala de un grupo de vertices según los siguientes parámetros:
    ```
    sprite.setGroup(group, obj, isAdd, _fn, _success, _step);
    ```
    • group. es un numero que indica el indice del grupo de vertices
    • obj. es un elemento de tipo {x,y,z,ax,ay,az,ex,ey,ez} que especifica las nuevas propiedades del grupo.
    • isAdd. si se especifica, es un booleano que indica si las nuevas propiedades son aditivas.
    • _fn. Si se especifica es la función ease con laque sucederá el cambio de especificaciones.
    • _success. si se especifica, es la función que se ejecuta cuando las nuevas especificaciones sucedan.
    • _step. es la velocidad con la que sucederán las nuevas especificaciones. Por default es uno (de inmediato). Por ejemplo, si se quisiera hacer en dos pasos, usaríamos 0.5
  • clone. permite clonar un sprite existente. A veces resulta útil clonar enemigos o efectos.
    ```
    sprite.clone();
    ```
  • join. permite fusionar un sprite en otro para construir sprites más complicados. Conserva los grupos de vertices sólo recorriendo el indice de grupo. Al fusionarlo podemos indicar la posición, ángulo y escala.
    ```
    sprite.join(obj,pos,ang,scale);
    ```
Eejecutar un efecto especial
Clona un sprite para animarlo y auto destruirlo al concluir la animación. (solo sprite es obligatorio)
```

enSp.fx(sprite,x,y,z,e,flip,endX,endY,endZ,step,startFrame,endFrame,callBackFnOnEnd,easeFn,onStep)
```
    • sprite. sprite recurso donde se hará el clon.
    • x. posición donde aparecerá.
    • y. posición donde aparecerá.
    • z. posición donde aparecerá.
    • e. escala que tendrá.
    • flip. no es nada.
    • endX. posición que alcanzará durante la animación
    • endY. posición que alcanzará durante la animación
    • endZ. posición que alcanzará durante la animación
    • step. velovidad con la que el puntero del cuadro avanza. Por default es 1.
    • startFrame. cuadro inicial de la animación
    • endFrame. cuadro final de la animación.
    • callBackFnOnEnd. funcion que se ejecuta justo al terminar la animación.
    • easeFn. funcion ease que se usa para suceder la traslación
    • onStep. funcion que se ejecuta a cada paso de la traslación (tEase, t)

Refrescar cuadro
Es importante refrescar el cuadro constantemente para que todos las acciones tomen efecto.
```
enSp.update()
```

Transformar ac3d en Arrays (ac3dToBuffer3d)

Con este chisme, es posible pasar del formato ac3d a uno de arrays de la siguiente forma:

- Array de vertices
- Array de indices (cada tres forma un triangulo)
- Array de mapa (la posición uv es de cada vértice y no por poligono)
- Array de grupo (si coloreas polígonos, aquí aparecerán las vertices que tiene que ver con esos poligonos)
- Array de polígonos por color (si coloreas polígonos, se agrupan los id de poligonos por color)

Sin más, aquí esta la herramienta:


viernes, 3 de junio de 2016

Curva Cuadrática

Ahora comparto la curva cuadrática.
https://jsfiddle.net/cincibeles/z0yq6ndr/

La función:

var QuadraticCurve =function(A,B,C){
var alphaX=A[0]-2*B[0]+C[0], betaX=2*(B[0]-A[0]), gamaX=A[0],
alphaY=A[1]-2*B[1]+C[1], betaY=2*(B[1]-A[1]), gamaY=A[1];
this.getPoint=function(t){
t=t || 0;
var t2=t*t, t_2=2*t,
        mx= t_2*alphaX + betaX,
        my= t_2*alphaY + betaY;
return [t2*alphaX +t*betaX +gamaX,
t2*alphaY +t*betaY +gamaY,
t, mx, my];
};

}

Nos devuelve un array [x, y, time, mx, my], donde x,y son la posición del punto en un tiempo t, y mx,my son los componentes de su pendiente (m = my/mx)

Y como plus, la función del Path..

var cuadraticPath=function(){
var args=[], i=0; while(arguments[i] instanceof Array) args.push(arguments[i++]);
var curves=[];
for(i=0; i<args.length; i+=2)
if(args[i]!=undefined && args[i+1]!=undefined && args[i+2]!=undefined){
curves.push(new QuadraticCurve(args[i],args[i+1],args[i+2]));
}
this.getPoint=function(time){
var i=time*curves.length, ii=~~i, jj=i%1;
if(!curves[ii]) return false;
return curves[ii].getPoint(jj); 
}
}

se usa

c=new cuadraticPath([1,1],[2,4],[4,1],[3,-1],[1,1]);
for(var i=0; i<=1; i+=0.1) console.log(c.getPoint(i));





viernes, 29 de enero de 2016

El punto está dentro del circuito pista

Reinauguro actividades en este mi blog que tenía tan abandonado con el tema funciones útiles.
Ejemp: https://jsfiddle.net/cincibeles/jsz1qoje/


Esta función permite saber si un punto se encuentra dentro de una pista circuito, siendo el punto una cordenada x,y y la pista dos arreglos (uno por cada orilla del circuito) de colección de puntos.

var isDotInRoad=function (point, vsOut, vsIn) {
    if((vsOut instanceof Array) && (vsIn instanceof Array)){
        var x = point[0], y = point[1], colionOut=[], colionIn=[], cntOut=0, cntIn=0;
        var xi, yi, xj, yj, xx;
        var inside = false;
        for (var i = 0, j = vsOut.length - 1; i < vsOut.length; j = i++) {
            xi = vsOut[i][0]; yi = vsOut[i][1]; xj = vsOut[j][0]; yj = vsOut[j][1];
            if ( (yi > y) != (yj > y) ){
                xx = (xj - xi) * (y - yi) / (yj - yi) + xi;
                if (x < xx){
                    inside = !inside;
                }else colionOut.push([j,i,xx]);
                cntOut++;
            }
        }
        for (var i = 0, j = vsIn.length - 1; i < vsIn.length; j = i++) {
            xi = vsIn[i][0]; yi = vsIn[i][1]; xj = vsIn[j][0]; yj = vsIn[j][1];
            if ( (yi > y) != (yj > y) ){
                xx = (xj - xi) * (y - yi) / (yj - yi) + xi;
                if (x < xx){
                    inside = !inside;
                }else colionIn.push([j,i,xx]);
                cntIn++;
            }
        }
        if(!inside && colionOut.length>0 && colionIn.length>0 && cntOut-colionOut.length + cntIn-colionIn.length>0){
            var a=colionOut.sort(sortByValue2).pop(), b=colionIn.sort(sortByValue2).pop();
            var ai=[vsOut[a[0]][0],vsOut[a[0]][1]], aj=[vsOut[a[1]][0],vsOut[a[1]][1]];
            var bi=[vsIn[b[0]][0],vsIn[b[0]][1]], bj=[vsIn[b[1]][0],vsIn[b[1]][1]];
            if(triangleNormal(point,ai,aj)*triangleNormal(point,bi,bj)<0) inside=true;
        }
        return inside;
    } return false;
};
            var triangleNormal=function(p,a,b){
                var i=a[0]-p[0]; var j=a[1]-p[1]; var k=b[0]-p[0]; var l=b[1]-p[1];
                return i*l-j*k;
            }
            var sortByValue2=function(a,b){ return a[2]-b[2]; }


y se usa así...

var road=[
[[0,5],[3,5],[3,1],[4,1],[4,5],[7,5],[7,2],[0,2]],
[[1,4],[2,4],[2,0],[5,0],[5,4],[6,4],[6,3],[1,3]]
];
var dot=[2.5,2.5];
var =isDotInRoad(dot,road[0],road[1]);


lunes, 8 de agosto de 2011

Idea para Juego de Plataformas tipo Game & Watch

Introducción.

Seguro has visto esos jugos modernos de acción donde se ve una calidad de gráficos sorprendentes!! Definitivamente el trabajo artístico se lleva buenos aplausos. Pero ¿Qué hay de la programación ?

Imagino que los algoritmos actuales con los que se programan los juegos modernos deben ser un tanto más complejos que los que se usaban en aquellos tiempos donde el control sólo tenía una palanca de cuatro direcciones y un botón. ... es más! aquellos que sólo constaban de uno o dos botones como los famosos Game&Watch.

Por otro lado, en la base del juego, el algoritmo que permite jugarlo, creo que no debe ser tan diferente entre el Game&Watch de 1980 y el más divertido y emocionante juego de acción actual.

A ciencia cierta no sé, pero puedo imaginarme jugando el clasico juego de Game&Watch donde caen martillos y debes esquivarlos, pero esta vez, entre movimiento y movimiento, al desaparecer y aparecer el monito en otra posición, introduzco una animación elaborada que muestre cómo éste monito se traslado hasta su nueva posición. Lo que sugiero es insertar animaciones que recreen el movimiento completo del Game&Watch a través de las distintas posiciones que adopte. Entonces, sería igual a cualquier juego moderno.

Sistema de plataformas Game and Watch

Dada la anterior situación, me he dado a la tarea de hacer un sencillo sistema de plataformas muy al estilo Game&Watch que me permita mover un personaje tipo Indiana Jones a través de un escenario usando sólo dos teclas de dirección, pero no es tan sencillo, porque a su vez, el escenario puede diseñarse con un arreglo (array) que consta de tres tipos de bloques: espacio vacío, espacio lleno y escaleras.

La idea del algoritmo es sencilla. Dependiendo de la posición del personaje se toma un muestreo de los bloques que se encuentren enfrente y dependiendo de su configuración se deduce cual será su siguiente posición.

Enseguida, un diagrama de las distintas configuraciones posibles que he delimitado. El punto de la flecha indica la posición inicial, la punta de la flecha indica la posición final, el trayecto hace referencia a el curso de la animación (animación para la cual fue diseñado el algoritmo).


En el diagrama se puede ver el funcionamiento básico del sistema de plataformas tipo Game&Watch.
Los cuadros naranjas repesentan espacios llenos, los durazno espacios vacios y los verdes escaleras. Las letras azules son los códigos con los que he querido identificarles.

La siguiente función escrita en JavaScript es en si misma el algoritmo mencionado anteriormente. Al indicarle un arreglo (array bidimensional con valores entre el 0 y el -2 ), una posición x,y (del monito dentro del arreglo), una dirección (izquierda: -1, derecha: 1) y un valor booleano extra (el cual veremos inmediatamente), arroja la nueva posición x,y además de código que identifica la configuración encontrada (false de no ser ninguna esperada).


//---- Función moverGWJones ----
function moverGWJones(map,x,y,d,b){
var a=false;
d=d>0?1:-1;
var m=[[],[],[],[],[],[]];
for(var iy=0;iy<6;iy++){
var iyy=iy-2+y;
for(var ix=0;ix<4;ix++){
var ixx=x+(ix*d);
m[iy][ix]=map[iyy]!=undefined?(map[iyy][ixx]!=undefined?map[iyy][ixx]:-1):-1;
}
}
var s=map[y+1]!=undefined?(map[y+1][x-d]!=undefined?map[y+1][x-d]:-1):-1;
if(m[3][0]<0){ //tiene piso
if(m[2][1]>=0){ //no hay pared
if(m[3][1]<0){ x+=d; a="0"; }else //hay suelo
if(b){ //no hay suelo
x+=d;y++;
if(m[4][1]<0){ a="1" }else{ a="2"; }
}else{
if(m[0][1]>=0 && m[0][2]>=0 && m[1][1]>=0 && m[1][2]<0){ x+=d;y--; a="A"; }else
if(m[1][1]>=0 && m[1][2]>=0 && m[2][2]<0){ x+=2*d;y--; a="B"; }else
if(m[2][2]>=0 && m[3][2]<0){ x+=2*d; a="C"; }else
if(m[3][2]>=0 && m[4][2]<0){ x+=2*d;y++; a="D"; }else
if(m[3][2]>=0 && m[4][2]>=0 && m[5][2]<0){ x+=2*d;y+=2; a="E"; }else
if(m[1][1]>=0 && m[1][2]>=0 && m[0][2]>=0 && m[0][3]>=0 && m[1][3]<0){ x+=2*d;y--; a="F"; }else
if(m[1][2]>=0 && m[1][3]>=0 && m[2][2]>=0 && m[2][3]<0){ x+=2*d; a="G"; }else
if(m[2][2]>=0 && m[2][3]>=0 && m[3][2]>=0 && m[3][3]<0){ x+=3*d; a="H"; }else
if(m[2][2]>=0 && m[3][2]>=0 && m[3][3]>=0 && m[4][2]>=0 && m[4][3]<0){ x+=3*d;y++; a="I"; }else
a=false;
}
}else{ //hay pared
if(m[1][1]==-2){ y--; a="O"; }else
if(m[1][0]>=0 && m[1][1]>=0){ x+=d;y--; a="J"; }else
if(m[0][0]>=0 && m[0][1]>=0 && m[1][0]>=0 && m[1][1]<0){ y--; a="K"; }else
a=false;
}
}else{
if(m[1][1]==-2){ y--; a="P"; }else
if(s==-2){ y++; a="Q"; }else
if(m[2][1]<0 && m[1][0]>=0 && m[1][1]>=0){ x+=d;y--; a="L"; }else
if(m[3][0]>=0 && m[4][0]<0){ y++; a="M"; }else
if(m[3][0]>=0 && m[4][0]>=0 && m[5][0]<0){ y+=2; a="N"; }else
a=false;
}
return [x,y,a];
}
//--- fin de la función ---


El valor booleano sirve para seleccionar una acción al aproximarse a un borde o caída: True para descender con cuidado, False para brincar.

Y Acá, un demo en vivo. La letra A representa al monito. El número 0 es un espacio lleno. El punto un espacio vacío. El signo + representa escaleras. Por otro lado. Use algunos sprites de Splinter Cell para hacer un ejemplo más completo a la derecha.




< Izquierda | Derecha > | Comportamiento Descender
Como puedes ver, es el mismo juego, sin embargo, en la derecha, la animación hace parecer que realmente se desplaza. Use ésta imagen gif sin loop. Son todas las animaciones, para aun lado y para el otro.

 
Por ultimo, puedes descargar aquí el demo para que veas el código funcionando. Espero te sea útil en tus proyectos y no olvides mencionarme si lo usas. n_n