En la misión final construiremos una escena jugable: una nave explora un mapa, evita asteroides, recoge núcleos, activa un portal y mantiene sincronizados cámara, colisiones, partículas e interfaz.
La nave debe recoger cuatro núcleos de energía y regresar al portal. Los asteroides reducen su escudo; los núcleos recuperan energía y habilitan la salida.
El proyecto prioriza una arquitectura clara: toda decisión visual nace de un estado matemático verificable.
El orden evita que la cámara siga una posición antigua o que el HUD muestre valores anteriores a una colisión.
La escena mantiene objetos simples:
Los elementos SVG no guardan las reglas: solamente representan estos datos.
Teclado y botones táctiles escriben el mismo estado de entrada. Las teclas WASD y las flechas generan un vector:
Normalizar antes de multiplicar por la rapidez conserva la misma velocidad en diagonal.
En lugar de asignar velocidad instantánea, aplicamos aceleración y resistencia:
La forma exponencial de la resistencia mantiene un comportamiento parecido con distintos frame rates.
Si la velocidad tiene longitud suficiente, el ángulo objetivo es atan2(vy,vx). El ángulo visual se interpola por el camino más corto para evitar giros completos inesperados.
El mapa tiene límites rectangulares. Al alcanzarlos, la posición se restringe y la componente normal de la velocidad se reduce:
Esto mantiene toda la región circular de la nave dentro del mundo.
Nave y asteroides usan círculos. Si existe penetración, se calcula la normal, se separa la nave y se refleja parte de la velocidad:
Un breve tiempo de invulnerabilidad evita descontar escudo en cada cuadro del mismo contacto.
Un núcleo se desactiva cuando la distancia entre centros no supera la suma de radios. El portal solo acepta la nave cuando todos están recogidos.
Esta regla lógica se representa cambiando brillo, color, texto y pulsación del portal.
La cámara sigue a la nave con suavizado exponencial. Todo el mundo se traslada respecto de su posición, mientras el HUD queda fuera de ese grupo:
Motor, recolecciones e impactos generan partículas. Cada una integra su velocidad, reduce vida y actualiza opacidad:
La retroalimentación visual debe reforzar un evento real, nunca reemplazar su estado lógico.
La misión termina al entrar en el portal activo. Si el escudo llega a cero, la nave reaparece y la misión se reinicia.
const limitar=(v,min,max)=>Math.max(min,Math.min(max,v));
function normalizar(v) {
const longitud=Math.hypot(v.x,v.y);
return longitud>1e-9
? {x:v.x/longitud,y:v.y/longitud}
: {x:0,y:0};
}
function factorSuave(respuesta,dt) {
return 1-Math.exp(-respuesta*dt);
}
console.log(normalizar({x:3,y:4}));
console.log(factorSuave(4,1/60).toFixed(4));Las salidas muestran el vector unitario (0.6,0.8) y un factor temporal estable.
function actualizarNave(nave,input,dt) {
const longitud=Math.hypot(input.x,input.y);
if (longitud>0) {
nave.vx+=input.x/longitud*nave.aceleracion*dt;
nave.vy+=input.y/longitud*nave.aceleracion*dt;
}
const freno=Math.exp(-nave.resistencia*dt);
nave.vx*=freno; nave.vy*=freno;
nave.x+=nave.vx*dt; nave.y+=nave.vy*dt;
}
const nave={x:0,y:0,vx:0,vy:0,aceleracion:500,resistencia:3};
actualizarNave(nave,{x:1,y:-1},1/60);
console.log(nave);La nave acelera en diagonal sin superar la magnitud prevista.
function separarCirculos(movil,fijo,restitucion=.35) {
const dx=movil.x-fijo.x,dy=movil.y-fijo.y;
const distancia=Math.hypot(dx,dy);
const suma=movil.radio+fijo.radio;
if (distancia>=suma || distancia<1e-9) return false;
const nx=dx/distancia,ny=dy/distancia;
const penetracion=suma-distancia;
movil.x+=nx*penetracion; movil.y+=ny*penetracion;
const vn=movil.vx*nx+movil.vy*ny;
if (vn<0) {
movil.vx-=(1+restitucion)*vn*nx;
movil.vy-=(1+restitucion)*vn*ny;
}
return true;
}
const nave={x:3,y:0,vx:-5,vy:0,radio:2};
console.log(separarCirculos(nave,{x:0,y:0,radio:2}),nave);La función separa la nave y corrige únicamente la componente de velocidad que entra en el obstáculo.
Usa WASD, las flechas o los botones. Recoge los cuatro núcleos, evita los asteroides y entra en el portal cuando cambie a verde.
Comprueba movimiento diagonal, pausas largas, contacto simultáneo con obstáculos, recolección rápida, límites del mapa y reinicios consecutivos. Una ampliación debe preservar estas garantías.
Conclusión: los gráficos 2D son una aplicación coordinada de álgebra, geometría y tiempo. Dominar las relaciones entre datos, espacios y actualización permite construir sistemas visuales correctos, expresivos y escalables.