68. Funciones en videojuegos

68.1 Introducción

Un videojuego combina reglas, gráficos, sonido, entradas del usuario y actualización constante del estado. Las funciones matemáticas ayudan a organizar esos cambios y a convertir decisiones en movimiento, colisiones, animaciones y resultados.

En este contexto, una función puede calcular la nueva posición de un personaje, verificar si dos objetos chocan, reducir la vida de un enemigo o decidir hacia dónde se mueve una entidad.

68.2 Estado del juego

El estado del juego reúne la información necesaria para continuar la partida: posición, velocidad, vida, puntaje, tiempo, nivel y cualquier otra variable relevante.

const estado = {
  tiempo: 0,
  puntaje: 0,
  jugador: { x: 40, y: 80, vida: 100 },
  enemigo: { x: 120, y: 80, vida: 50 }
};

console.log("Jugador:", estado.jugador);
console.log("Enemigo:", estado.enemigo);
console.log("Puntaje:", estado.puntaje);

Cuanto más claro sea el estado, más fácil será escribir funciones que lo actualicen sin confusión.

68.3 Bucle de juego

La mayoría de los videojuegos tienen un ciclo que se repite muchas veces por segundo. Ese ciclo procesa entrada, actualiza el mundo y dibuja el resultado.

function actualizar(estado) {
  return {
    tiempo: estado.tiempo + 1,
    puntaje: estado.puntaje + 10
  };
}

let estado = { tiempo: 0, puntaje: 0 };

for (let frame = 1; frame <= 5; frame++) {
  estado = actualizar(estado);
  console.log("Frame", frame, estado);
}

Cada vuelta del ciclo suele llamarse frame o fotograma.

68.4 Movimiento del jugador

El movimiento puede modelarse con una función que recibe la posición, la dirección y la velocidad.

function moverJugador(jugador, entrada, velocidad) {
  return {
    x: jugador.x + entrada.x * velocidad,
    y: jugador.y + entrada.y * velocidad
  };
}

let jugador = { x: 10, y: 20 };
const entrada = { x: 1, y: -1 };

jugador = moverJugador(jugador, entrada, 5);
console.log(jugador);

La entrada puede venir del teclado, mouse, pantalla táctil o un control.

68.5 Movimiento con delta de tiempo

Usar dt permite que el movimiento dependa del tiempo real y no solamente de la cantidad de frames.

function mover(posicion, velocidad, dt) {
  return posicion + velocidad * dt;
}

let x = 0;
const velocidad = 120;
const dt = 1 / 60;

for (let frame = 1; frame <= 5; frame++) {
  x = mover(x, velocidad, dt);
  console.log("Frame", frame, "x:", x.toFixed(2));
}

Esto evita que el juego se vuelva más rápido o más lento según el rendimiento de la computadora.

68.6 Normalizar dirección

Si un jugador se mueve en diagonal sumando x e y, puede avanzar más rápido que en línea recta. Para evitarlo se normaliza el vector de dirección.

function normalizar(v) {
  const longitud = Math.sqrt(v.x * v.x + v.y * v.y);

  if (longitud === 0) {
    return { x: 0, y: 0 };
  }

  return { x: v.x / longitud, y: v.y / longitud };
}

const direccion = normalizar({ x: 1, y: 1 });
console.log(direccion);

68.7 Mantener objetos dentro del escenario

Una función de límite impide que un personaje salga del área permitida.

function limitar(valor, minimo, maximo) {
  return Math.max(minimo, Math.min(maximo, valor));
}

function limitarJugador(jugador, ancho, alto) {
  return {
    x: limitar(jugador.x, 0, ancho),
    y: limitar(jugador.y, 0, alto)
  };
}

const jugador = { x: 450, y: -20 };
console.log(limitarJugador(jugador, 400, 300));

68.8 Saltos y gravedad

Un salto se puede simular con velocidad vertical y gravedad. La gravedad modifica la velocidad, y la velocidad modifica la posición.

function actualizarSalto(jugador, dt) {
  const gravedad = 980;
  let vy = jugador.vy + gravedad * dt;
  let y = jugador.y + vy * dt;
  let enSuelo = false;

  if (y > 0) {
    y = 0;
    vy = 0;
    enSuelo = true;
  }

  return { y, vy, enSuelo };
}

let jugador = { y: 0, vy: -420, enSuelo: false };

for (let i = 1; i <= 5; i++) {
  jugador = actualizarSalto(jugador, 1 / 60);
  console.log(i, jugador);
}

68.9 Proyectiles

Un proyectil se actualiza con una función de movimiento. También se suele verificar si salió del escenario para eliminarlo.

function actualizarProyectil(proyectil, dt) {
  return {
    x: proyectil.x + proyectil.vx * dt,
    y: proyectil.y + proyectil.vy * dt,
    vx: proyectil.vx,
    vy: proyectil.vy
  };
}

function estaFuera(proyectil, ancho, alto) {
  return proyectil.x < 0 || proyectil.x > ancho || proyectil.y < 0 || proyectil.y > alto;
}

let proyectil = { x: 20, y: 50, vx: 180, vy: 0 };
proyectil = actualizarProyectil(proyectil, 0.5);

console.log(proyectil);
console.log("Fuera:", estaFuera(proyectil, 100, 100));

68.10 Distancia entre entidades

La distancia entre dos entidades permite decidir si están cerca, si un enemigo puede atacar o si un objeto puede recogerse.

function distancia(a, b) {
  const dx = b.x - a.x;
  const dy = b.y - a.y;
  return Math.sqrt(dx * dx + dy * dy);
}

const jugador = { x: 30, y: 40 };
const llave = { x: 42, y: 49 };

console.log("Distancia:", distancia(jugador, llave).toFixed(2));
console.log("Puede recoger:", distancia(jugador, llave) <= 15);

68.11 Colisiones rectangulares

Una colisión común en juegos 2D es la intersección entre rectángulos alineados a los ejes.

function colisionRectangular(a, b) {
  return a.x < b.x + b.ancho &&
         a.x + a.ancho > b.x &&
         a.y < b.y + b.alto &&
         a.y + a.alto > b.y;
}

const jugador = { x: 20, y: 20, ancho: 30, alto: 40 };
const enemigo = { x: 45, y: 30, ancho: 30, alto: 30 };

console.log("Colisionan:", colisionRectangular(jugador, enemigo));

68.12 Colisiones circulares

Las colisiones circulares se usan para objetos redondos o para aproximar entidades con una zona de contacto simple.

function colisionCircular(a, b) {
  const dx = b.x - a.x;
  const dy = b.y - a.y;
  const sumaRadios = a.radio + b.radio;
  return dx * dx + dy * dy <= sumaRadios * sumaRadios;
}

const pelota = { x: 10, y: 10, radio: 8 };
const moneda = { x: 23, y: 12, radio: 5 };

console.log("Colisionan:", colisionCircular(pelota, moneda));

68.13 Daño y vida

Las funciones también expresan reglas del juego. Por ejemplo, recibir daño reduce la vida, pero no debería bajarla por debajo de cero.

function aplicarDanio(entidad, danio) {
  return {
    ...entidad,
    vida: Math.max(0, entidad.vida - danio)
  };
}

let enemigo = { nombre: "Robot", vida: 35 };
enemigo = aplicarDanio(enemigo, 12);
enemigo = aplicarDanio(enemigo, 30);

console.log(enemigo);

68.14 Puntuación

El puntaje suele depender de acciones: recoger objetos, derrotar enemigos, completar objetivos o terminar un nivel rápido.

function calcularPuntos(evento) {
  const valores = {
    moneda: 10,
    enemigo: 100,
    bonus: 250
  };

  return valores[evento] || 0;
}

let puntaje = 0;
puntaje += calcularPuntos("moneda");
puntaje += calcularPuntos("enemigo");

console.log("Puntaje:", puntaje);

68.15 Enemigos que persiguen

Una IA simple puede mover un enemigo hacia el jugador calculando la dirección entre ambos.

function moverHacia(origen, destino, velocidad) {
  const dx = destino.x - origen.x;
  const dy = destino.y - origen.y;
  const distancia = Math.sqrt(dx * dx + dy * dy);

  if (distancia === 0) {
    return origen;
  }

  return {
    x: origen.x + dx / distancia * velocidad,
    y: origen.y + dy / distancia * velocidad
  };
}

const enemigo = { x: 10, y: 10 };
const jugador = { x: 40, y: 50 };

console.log(moverHacia(enemigo, jugador, 5));

68.16 Patrullaje

Un enemigo puede patrullar entre dos puntos usando una función que invierte la dirección al llegar a los límites.

function patrullar(enemigo, minimo, maximo) {
  let x = enemigo.x + enemigo.direccion * enemigo.velocidad;
  let direccion = enemigo.direccion;

  if (x >= maximo || x <= minimo) {
    direccion *= -1;
    x = Math.max(minimo, Math.min(maximo, x));
  }

  return { x, direccion, velocidad: enemigo.velocidad };
}

let enemigo = { x: 48, direccion: 1, velocidad: 4 };

for (let i = 1; i <= 4; i++) {
  enemigo = patrullar(enemigo, 20, 50);
  console.log(enemigo);
}

68.17 Probabilidad de aparición

La aleatoriedad permite decidir si aparece un enemigo, un objeto o una recompensa. Conviene encapsular esa regla en una función.

function ocurre(probabilidad) {
  return Math.random() < probabilidad;
}

let apariciones = 0;

for (let intento = 1; intento <= 10; intento++) {
  if (ocurre(0.3)) {
    apariciones++;
  }
}

console.log("Apariciones:", apariciones);

68.18 Niveles de dificultad

La dificultad puede expresarse mediante funciones que escalan valores según el nivel actual.

function vidaEnemigo(nivel) {
  return 50 + nivel * 15;
}

function velocidadEnemigo(nivel) {
  return 2 + nivel * 0.4;
}

for (let nivel = 1; nivel <= 5; nivel++) {
  console.log(
    "Nivel", nivel,
    "vida:", vidaEnemigo(nivel),
    "velocidad:", velocidadEnemigo(nivel).toFixed(1)
  );
}

68.19 Enfriamiento de acciones

Un enfriamiento evita que una acción se repita sin límite. Se usa para disparos, habilidades, ataques y recolecciones.

function puedeUsarHabilidad(tiempoActual, ultimoUso, cooldown) {
  return tiempoActual - ultimoUso >= cooldown;
}

const cooldown = 3;
let ultimoUso = 5;

for (let tiempo = 6; tiempo <= 9; tiempo++) {
  console.log("t =", tiempo, puedeUsarHabilidad(tiempo, ultimoUso, cooldown));
}

68.20 Interpolación en cámaras

Una cámara puede seguir al jugador de manera suave interpolando entre su posición actual y el objetivo.

function acercar(actual, objetivo, factor) {
  return actual + (objetivo - actual) * factor;
}

let camara = { x: 0, y: 0 };
const jugador = { x: 100, y: 60 };

for (let frame = 1; frame <= 5; frame++) {
  camara = {
    x: acercar(camara.x, jugador.x, 0.2),
    y: acercar(camara.y, jugador.y, 0.2)
  };

  console.log("Cámara:", camara);
}

68.21 Animaciones por tiempo

Las animaciones pueden elegir un cuadro según el tiempo transcurrido. Una función convierte tiempo en índice de imagen.

function cuadroAnimacion(tiempo, cuadros, duracionCuadro) {
  return Math.floor(tiempo / duracionCuadro) % cuadros;
}

for (let i = 0; i <= 8; i++) {
  const tiempo = i * 0.1;
  console.log("t:", tiempo.toFixed(1), "cuadro:", cuadroAnimacion(tiempo, 4, 0.2));
}

68.22 Separar reglas y presentación

Las funciones que calculan reglas del juego no deberían depender directamente de cómo se dibuja la pantalla. Esto facilita cambiar gráficos sin romper la lógica.

function estaVivo(entidad) {
  return entidad.vida > 0;
}

function colorSegunVida(entidad) {
  if (entidad.vida > 60) return "verde";
  if (entidad.vida > 25) return "amarillo";
  return "rojo";
}

const jugador = { vida: 40 };

console.log("Regla:", estaVivo(jugador));
console.log("Presentación:", colorSegunVida(jugador));

68.23 Simulador Interactivo: MathQuest en Acción

Para observar todas estas funciones trabajando de manera conjunta en tiempo real, hemos desarrollado el simulador MathQuest: Ecuaciones en Acción. Controla al personaje en este entorno neo-futurista usando tu teclado (A, D / Flechas para moverte, W / Flecha Arriba / Espacio para saltar y F / Clic para disparar). El panel Math Inspector a la derecha extrae directamente las variables internas del estado de juego y muestra qué fórmulas matemáticas exactas se están calculando en cada fotograma.

MathQuest: Ecuaciones en Acción

Nivel 1

MathQuest

Aprende las 22 funciones matemáticas de los videojuegos interactuando en tiempo real.

❤️
⭐ Puntos: 0
LISTO ⚡

Objetivo del Juego

  • Esquiva slimes y murciélagos.
  • Usa F o Clic para disparar.
  • Consigue la llave (🔑) y llévala a la puerta (🚪).

68.2 Estado del Juego const estado = {...}

Tiempo de Juego: 0.00s
Puntaje acumulado: 0
Posición Jugador (x, y): 0px, 0px
Velocidad (vx, vy): 0px/s, 0px/s
¿En suelo firme?: true

68.6 Normalizar Dirección v / |v|

const long = Math.sqrt(v.x * v.x + v.y * v.y); return { x: v.x / long, y: v.y / long };
Entrada bruta del teclado: (0, 0)
Dirección normalizada (u): (0, 0)

68.7 Mantener en Escenario Math.max/min

x = Math.max(0, Math.min(ancho, x));

Limita la posición del personaje para que no pueda caer al vacío ni salir del nivel horizontal (1200px).

68.8 Saltos y Gravedad vy += g * dt

vy += gravedad * dt; y += vy * dt;
Fuerza física actual: GRAVEDAD = 1100 px/s²
Salto en tiempo real: y = 0, vy = 0

68.10 Distancia Euclidiana sqrt(dx²+dy²)

const dx = b.x - a.x; const dy = b.y - a.y; return Math.sqrt(dx * dx + dy * dy);
Distancia a la Llave (🔑): 0.00 px
Rango de atracción (<60px): NO

68.18 Escala de Dificultad Nivel f(lvl)

vida = 30 + nivel * 15; velocidad = 1.2 + nivel * 0.3;
Dificultad actual: Nivel 1
Vida base de enemigos: 45 HP
Velocidad de patrulla/vuelo: 1.5 px/s

68.21 Animaciones por Tiempo Math.floor

frame = Math.floor(tiempo / duracion) % cuadros;
Reloj del juego: t = 0s
Sprite Frame index: 0

68.24 Errores comunes

Al usar funciones en videojuegos conviene evitar estos errores:

  • Atar la velocidad del juego a la cantidad de frames.
  • Duplicar fórmulas de movimiento en muchas partes del código.
  • Mezclar reglas de juego con código de dibujo.
  • No limitar posiciones, vida, energía o puntajes.
  • Ignorar casos extremos, como distancia cero o velocidades muy grandes.

68.25 Conclusión

Los videojuegos usan funciones para transformar entradas en acciones, reglas en consecuencias y tiempo en movimiento. Al escribir funciones pequeñas y claras, el comportamiento del juego se vuelve más fácil de probar, ajustar y ampliar.

La matemática no aparece como teoría aislada: aparece en cada movimiento, colisión, animación, cámara, recompensa y decisión del sistema.

Volver al índice