Juego de meteoros en menos de 1K de Javascript
Desde que el
concurso JS1K
comenzó en 2010 (donde obtuve el segundo lugar con mi
ajedrez Javascript de 1K) he visto varias
participaciones intentando imitar un juego de meteoros. Me dio curiosidad el
tema, ¿de verdad era tan complicado hacer algo bonito y completo? era un buen
desafío para mi tiempo libre :)
Click en la nave para comenzar a jugar. Use las flechas y la barra de espacio
Comencé a escribirlo el 23 de febrero del 2012 pensando en
el concurso JS1K 2012 edición con tema del amor. La mayor parte del juego se
codificó en una semana, incluyendo todas las caracteristicas básicas: nave
del jugador, meteoros, chispas, naves enemigas con IA y múltiples niveles.
No fue tan fácil como pensé, de hecho necesitaba más bytes
para comprimir todo lo que yo quería, así que decidí utilizar
JS-Crunch de
@aivopass, esto me dio más espacio
para el diseño, irónicamente para un juego en el espacio :).
Me perdí el concurso del 2012 debido a que no estaba
contento con el resultado y rediseñé varias cosas para el concurso de otoño
2012, pero ¡olvidé enviarlo!
Así que una semana después del inicio del JS1K 2013 de
primavera, me las arreglé para meter un indicador de puntuación y envié mi
participación, fue publicada como el número 1312. Vea la
versión original.
Así como el concurso se desarrollaba noté que la nave
del jugador no tenía inercia mientras rotaba, así que hice lo posible para
meter unos pocos bytes más y lo reenvié:
mejorado con inercia.
Y aún no estaba contento, porque quería agregar un halo
«radioactivo» alrededor de los gráficos que estaba presente antes de que
agregara la puntuación. Afortunadamente encontré
los tips de compresión
Javascript de ClaudioCC.
Con todos los bytes ahorrados pude poner de vuelta la
sombra y también algunas pequeñas mejoras, todo esto se encuentra en la
versión final.
Desafortunadamente mi juego no llegó al top 10 final
publicado el 5 de mayo del 2013.
Características y uso
Pilotee su nave a través del espacio y elimine los meteoros.
Rote con las flechas izquierda y derecha, avance con la flecha de arriba,
dispare con la barra de espacio.
Cuidado con los alienígenas acechando.
Si desea cambiar la configuración inicial sólo tiene que
refrescar la página.
- Vectorial a colores escala 16:9
- Cuenta la puntuación :)
- Muchas chispas en explosiones
- Juegos siempre diferentes
- 30 cuadros por segundo en la mayoría de las computadoras
- Los objetos tienen transición suave entre bordes
- Al acabar con los meteoros se obtienen más.
- Mucho realismo ya que no hay sonidos en el espacio.
- Los alienígenas también derriban meteoros y otros alienígenas, seguro por accidente.
- También puede servir como protector de pantallas, sólo déjelo correr.
- Inercia mientras rota :)
- Halo verde "radioactivo" :D
Código fuente
Note que el JS1K "provee" algunas variables iniciadas con datos
importantes, similar a esto:
<canvas id="c">
</canvas><script>
var c=document.getElementById("c");
var a=c.getContext("2d");
</script>
<script src="meteors.js">
</script>
Aquí está mi código fuente original comentado.
p=[],
//
// Meteoros en 1K de Javascript (después de compresión)
// (c) 2012-2013 por Oscar Toledo G. (@nanochess)
// http://www.nanochess.org/
//
// Creación: 23-feb-2012.
// Última revisión: 29-mar-2013.
//
// En este código fuente varias expresiones están repetidas para obtener más
// compactación del compresor JS de @aivopaas. http://js1k.com/demo/1127
// y posteriormente http://www.iteral.com/jscrush/
//
// Arreglo de objetos visuales (1a. línea para evadir bug en compresor 1127)
// Arreglo para el teclado
q=[],
// <canvas> ancho/altura, salva además para uso posterior
c.width=d=444,
c.height=h=252,
// window.onkeydown/onkeyup, anota tecla oprimida/soltada
onkeydown=function(w){
q[w.which]=.1};
// No elimine punto y coma al final de ambas funciones o falla después de comprimir
onkeyup=function(w){
q[w.which]=0};
// Compresión de los nombres de función de <canvas>
for(Z in a)a[Z[0]+(Z[6]||Z[2])]=a[Z];
// Añade un nuevo objeto a la lista visual
function o(x,y,r,a,c,s,o,t){
p[p.length]={
// Coordenadas X,Y
x:x,y:y,
// Rotación (en radianes) para visualizar
r:r,
// Rotación (en radianes) para desplazamiento
w:r,
// Escala
u:c,
// Puntuación (nave) o quién disparó esta bala (3=nave, 0=enemigo)
s:t,
// Tipo de objeto
// 0 - Nave del jugador
// 1 - Nave enemiga
// 2 - Meteoro
// 3 - Chispa
// 4 - Bala
t:s,
// Velocidad
v:a,
// Duración
z:o}
}
// Bucle principal llamado continuamente
setInterval(
// Función conteniendo el bucle principal
function(){
// Mantiene contexto canvas en alcance
with(a)
with(
// Líneas gordas para todos los objetos
lineWidth=2,
// Limpia el canvas
fc(0,0,d,h),
// Si lista vacía o nave del jugador fue eliminada entonces reinicia
p.length&&!p[0].t||
// Centra nave del jugador, apunta arriba
o(d/2,h/2,p.length=0,0,1,0,1,0),
// Añade objeto Math al alcance
Math)
// Bucle a través de la lista visual en reversa para no tener problemas con borrado
for(e=p.length;e--;)
// Incluye objeto visual en alcance (ahora 3 objetos en alcance!)
with(p[e]){
// Salva contexto antes de dibujar objeto
sv();
// Sombra amplia
shadowBlur=9,
// Color para puntuación, notese que reusa cadena
fillStyle=
// Propiedades de sombra, verde radioactivo :P lo hace lucir mejor
shadowColor="#5f0",
// Selecciona color para objeto, el 3er. caracter es compartido entre objetos :)
strokeStyle="#"+"fcfdafd62f8".substr(t+t,3);
// ¿Es un enemigo?
2-t||
// ...sí, inercia, y ¿ahora quieto?
(v*=.97)<.1&&
// ...sí, dispara al jugador
o(x,y,
// ...bala directa al jugador...
atan2(p[0].x-x,y-p[0].y,
// ...rota el enemigo a la izq/der aleatorio...
w=r+=random()-.5,
// ...escoge velocidad aleatoria para enemigo, actualiza desplazamiento...
v=4*random())
// ...ángulo de bala aleatorio entre -0.2 y 0.2 radianes de la dirección exacta
// para no hacerlo muy difícil...
+.4*random()-.2,
// ...note que no incluye último argumento, esto señala bala disparada por enemigo
7,1,4,35);
// ¿Es el jugador?
t||(
// ...inercia...
v*=.97,
// Oprimir espacio dispara balas, reutiliza tiempo para tirar más lento
q[32]&&!z&&o(x,y,r,
// Note que último argumento señala bala disparada por nave
7,1,4,35,z=3),
// Visualiza puntuación, note que reutiliza números
fx(s,4,20),
// Oprimir flechas izq/der rota la nave del jugador
r+=-q[37]||q[39]||0,
// Escoge meteoro o nave enemiga
i=random()<.2,
// Añade objetos en posiciones aleatorias cuando hay menos de 6 objetos
// Note que los objetos se añaden lejos y alrededor de la nave del jugador
p.length<6&&
o(x+d/4+.4*random()*d,
y+h/4+.4*random()*h,7*random(),1,i?1:4,i+1),
// Oprimir flecha de arriba acelera la nave y actualiza desplazamiento
q[38]&&(w=r,v=3)
);
// Traslada para dibujar y calcular nueva posición,
// mantiene dentro del canvas con margen para entrada suave
ta(x=(x+d+sin(w)*v+15*u)%(u*10+d)-u*5,
y=(y+h-cos(w)*v+15*u)%(u*10+h)-u*5);
// Aplica rotación
rt(r);
// Aplica escala
sa(u+.1,u+.1);
// Comienza a dibujar
ba();
// Cada figura contenida en un solo número
// 2602934 = 8,8-4,6-0,8-4,0 (nave apuntando arriba)
// 26573945564232 = 0,5-2,8-4,7-7,8-8,2-7,0-4,1-1,0 (meteoro)
// 39023120720 = 2,4-0,7-8,7-6,4-6,1-2,1 (parecido a cápsula Apolo :P)
// 207489 = 3,5-5,5-4,3 (triángulo apuntando arriba)
// 207489 = 3,5-5,5-4,3 (lo mismo)
for(i=[2602934,26573945564232,39023120720,207489,207489][t];
// el número se vuelve cero al terminar el dibujo
i;
// Extrae un par de coordenadas. Modulo 9 da unidad 0-8 (espacio de dibujo)
i-=f=i%9,i/=9,
// ...substrae unidad antes de división por 9 para mantener entero
i-=g=i%9,i/=9,
// dibuja línea
ln(f-4,g-4));
// Cierra la figura
ca();
// Pinta la figura
sr();
// Restaura el contexto
re();
// Si manejando nave del jugador activa o bala disparada hace un cuadro
// Checa colisión contra otros objetos
for(f=!t*u|3<t&z<35?p.length:0;f--;)
// Se asegura que no sea el mismo objeto y sea nave o meteoro...
if(f-e&&p[f].t<3
// ...dentro de caja de colisión
&abs(p[f].x-x)<4*p[f].u&abs(p[f].y-y)<4*p[f].u)
// Ok, hay una colisión
// Si es el jugador entonces marca como muerto y prepara para
// desaparecer en unos segundos, otros objetos dan puntuación
// si es bala del jugador y desaparecen en próximo cuadro.
// Pone el objeto colisionado en alcance.
// (¡ahora cuatro objetos en alcance!, no haga esto en casa)
with(z=e?(s&&++p[0].s,1):(u=0,99),p[f])
// Añade chispa 1
o(x,y,7*random(),9,1,3,9),
// Añade chispa 2
o(x,y,7*random(),9,1,3,9),
// Añade chispa 3
o(x,y,7*random(),9,1,3,9),
// Añade chispa 4
o(x,y,7*random(),9,1,3,9),
// Añade chispa 5
o(x,y,7*random(),9,1,3,9),
// Añade chispa 6
o(x,y,7*random(),9,1,3,9),
// El primer meteoro recibe un impulso que lo gira
r+=random(),
// ¿Debe cortar el meteoro en dos?
--u?
// Crea un segundo meteoro con más impulso
o(x,y,r+random(),
// Ambos meteoros aceleran y obtienen el mismo tamaño pequeño
++v,u,1)
// Se prepara para eliminar objeto, ya u=0 si 'f' apunta a nave del jugador
:z=f?1:99;
// ¿Tiempo de borrar objeto? Elimina objeto de lista visual
z&&!--z&&e|!u&&p.splice(e,1)
}
// Fin de la función
}
// Refresca 30 veces por segundo (1000/30 = 33 milliseconds)
,33);
Enlaces relacionados
Última modificación: 12-may-2013