fogón

← BibliotecaKitmormon/starter-racing

Kit de inicio: carreras

m@mormonKit, MIT
v1hace 2 días

Obra original de Kenney ↗

Clonar
FOGONfogon pieces add starter-racing
GITgit clone https://git.fogon.gg/mormon/starter-racing.git
REMIXfogon new starter-racing --from starter-racing
ZIPstarter-racing.zipBajar
Controles

Kit de inicio: carreras

El Starter Kit Racing de Kenney, que está hecho para Godot, portado a three.js y TypeScript para Fogón: el mismo circuito, la misma manera de manejar y los mismos números, con sus modelos y sonidos originales. Un circuito armado con piezas sobre una grilla y un vehículo que derrapa, se inclina en las curvas, larga humo y suena según cómo va.

La camioneta amarilla doblando después de la largada

En la demo: W (o ↑) acelera, S (o ↓) frena y después va marcha atrás, A y D (o ← →) doblan. Arriba se elige el vehículo, también con 1 a 5: cuatro camionetas y una moto, que se maneja igual pero se acuesta entera en las curvas. R vuelve a la largada. En el teléfono hay cuatro botones: doblar a la izquierda, pedales a la derecha.

Es solo la página. No hay sala ni red, ni vueltas contadas, ni rivales: es una base para empezar un juego propio, no un juego terminado.

Cómo está hecho

Lo que se mueve no es el vehículo: es una bola que rueda. El motor la hace girar, el agarre del piso convierte ese giro en velocidad, y el modelo se dibuja donde está la bola, mirando hacia donde apunta el volante. Por eso derrapa: cuando el modelo dobla, la bola sigue rodando un momento para donde iba. En el kit esa bola la mueve la física de Godot; acá la mueve physics.ts, a mano y con los mismos números (masa, gravedad, freno del giro, agarre), sin ningún motor de física.

Qué trae

client/racing es lo que se lleva.

ArchivoQué hace
vehicle.tsVehicle y Motorcycle: el manejo (step(dt, volante, acelerador)), la inclinación de la carrocería, las ruedas, el humo y lo que suena. Son el vehicle.gd y el vehicle-motorcycle.gd del kit.
physics.tsBall (la bola que rueda) y Collider (pisos planos y triángulos contra los que rueda y choca). Sin motor ni three.js: solo números.
track.tsEl circuito: TRACK (el del kit, escrito como texto), parseTrack, buildTrack (lo dibuja y pone sus paredes en el Collider), PIECES y addMeshCollision.
walls.tsLas paredes de la recta y de la curva, como triángulos (las formas de colisión del kit).
view.tsView: la cámara que sigue al vehículo y se aleja cuando va rápido (el view.gd del kit).
trail.tsTrail: el humo del derrape.
audio.tsVehicleSounds: el motor y el chirrido en bucle (cambian de tono y volumen) y el golpe.
environment.tsEnvironment: el fondo, la luz de ambiente, el sol con sombras y la misma curva de color que usa Godot («Filmic»).
assets.tsDe dónde salen los archivos: loadModel, loadTexture, url y setBase.

Lo demás es la demo y sirve de ejemplo: client/main.ts tiene el teclado, los botones táctiles, el cambio de vehículo y el bucle del juego.

Cómo se usa en un juego

fogon pieces add starter-racing

Eso trae client/racing/ (solo código) y agrega three.js al package.json. Los modelos, el humo y los sonidos no viajan con el juego: se cargan desde la demo publicada de esta pieza (https://play.fogon.page/starter-racing/racing/).

import * as THREE from "three";
import { loadModel } from "./racing/assets";
import { VehicleSounds } from "./racing/audio";
import { Environment } from "./racing/environment";
import { Collider } from "./racing/physics";
import { buildTrack, parseTrack, TRACK } from "./racing/track";
import { Vehicle } from "./racing/vehicle";
import { View } from "./racing/view";

const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
const scene = new THREE.Scene();
const environment = new Environment(scene, renderer);
const view = new View();
scene.add(view.object);

// El mundo: un piso plano de 60 m (agarra mucho: 5) y el circuito, que pone sus paredes en el collider.
const collider = new Collider();
collider.addFloor(0, -30, -30, 30, 30, { friction: 5, bounce: 0, body: 0 });
scene.add(await buildTrack(parseTrack(TRACK), collider));

const sounds = new VehicleSounds();
const vehicle = new Vehicle(await loadModel("vehicle-truck-yellow"), collider, { sounds });
vehicle.place(3.5, 0, 5); // sobre la línea de largada, mirando a +z
scene.add(vehicle.object, vehicle.smoke);
addEventListener("keydown", () => sounds.unlock()); // el navegador solo deja sonar después de un toque o una tecla

// El juego va en pasos de un sesentavo de segundo, se dibuje la pantalla cada cuanto se dibuje.
const STEP = 1 / 60;
let owed = 0;
let last = performance.now();
renderer.setAnimationLoop((now) => {
  const dt = Math.min(0.1, (now - last) / 1000);
  last = now;
  owed += dt;
  while (owed >= STEP) {
    owed -= STEP;
    vehicle.step(STEP, steer, throttle); // volante: −1 izquierda … 1 derecha · acelerador: −1 atrás … 1 adelante
    view.update(STEP, vehicle.object.position, vehicle.linearSpeed);
  }
  vehicle.update(dt); // el humo
  environment.follow(view.object.position, 30); // las sombras, alrededor del vehículo
  renderer.render(scene, view.camera);
});

La moto: new Motorcycle(await loadModel("vehicle-motorcycle"), collider, { sounds }) y sounds.setEngine("audio/engine-motorcycle.ogg"). Las otras camionetas: vehicle-truck-green, -purple y -red.

Cambiar la pista

El circuito es un texto: una fila de la grilla por línea, y cada casilla es una letra (la pieza) y un número (cuántos cuartos de vuelta está girada). Cambiar el dibujo es cambiar la pista.

import { buildTrack, parseTrack, tileCenter } from "./racing/track";

// S recta · C curva · F largada · f bosque · t carpas · . piso pelado
// La recta va de norte a sur con 0 o 2 giros. La curva une oeste y sur con 0 giros; después sur y este (1),
// este y norte (2), norte y oeste (3).
const OVAL = `
f0 f0 f0 f0 f0
f0 C1 S1 C0 f0
f0 F0 t0 S0 f0
f0 C2 S1 C3 f0
f0 f0 f0 f0 f0
`;
scene.add(await buildTrack(parseTrack(OVAL, { x: -2, z: -2 }), collider)); // (x, z): la casilla de la primera letra
const start = tileCenter(-1, 0); // el centro de la largada, en metros
vehicle.place(start.x, 0, start.z, Math.PI); // mirando al norte (−z)

Cada casilla mide 7,49 m de lado (TILE). El piso de addFloor tiene que cubrir toda la pista: las piezas solo traen las paredes. Las piezas están en PIECES: para sumar una, agregale una letra con su modelo y, si frena a los vehículos, sus paredes como triángulos (o pasale a buildTrack tu propia tabla como tercer argumento).

Rampas, lomas y obstáculos

Cualquier cosa dibujada puede volverse algo sobre lo que se rueda y contra lo que se choca:

import { addMeshCollision } from "./racing/track";

const bump = await loadModel("track-bump"); // viene con el kit, y no está en el circuito
bump.position.set(3.7, 0, 12);
scene.add(bump);
addMeshCollision(collider, bump, { friction: 5, bounce: 0, body: 2 });

friction es cuánto agarra (el piso: 5; las paredes: 0, para que el vehículo resbale contra ellas en vez de trabarse) y bounce cuánto rebota. body distingue una cosa de otra: el vehículo avisa (el golpe) cuando empieza a tocar una que no estaba tocando.

Usar los archivos sin depender de la demo

Para tenerlos dentro de tu juego (sin conexión, o servidos por vos), bajalos a public/racing/ y decíselo al cargador una vez, antes de cargar nada:

mkdir -p public/racing/models/Textures public/racing/sprites public/racing/audio && cd public/racing
B=https://play.fogon.page/starter-racing/racing
for m in track-straight track-corner track-finish track-bump track-tents decoration-empty decoration-forest decoration-tents \
  vehicle-truck-yellow vehicle-truck-green vehicle-truck-purple vehicle-truck-red vehicle-motorcycle; do
  curl -fo models/$m.glb $B/models/$m.glb
done
curl -fo models/Textures/colormap.png $B/models/Textures/colormap.png
curl -fo sprites/smoke.png $B/sprites/smoke.png
for s in engine engine-motorcycle skid impact; do curl -fo audio/$s.ogg $B/audio/$s.ogg; done
import { setBase } from "./racing/assets";
setBase("./racing/");

Son 1,5 MB.

Para hacerlo entre varios

Ball y Collider no dependen de nada: el mismo physics.ts puede correr en server/room.ts. Lo más simple es que cada jugador mueva su vehículo y mande dónde está (un campo propio con velocidad máxima, como en la plantilla de Fogón), y que la sala lleve las vueltas y las posiciones.

Qué quedó distinto del original

  • La física es propia. El kit usa Jolt (la de Godot); acá la bola se mueve con unas pocas cuentas hechas a mano. Los números son los del kit y se maneja parecido (unos 10 m/s a fondo, una vuelta en unos 19 segundos), pero no es la misma simulación: los choques y los derrapes no van a salir idénticos.
  • Las paredes resbalan. En el kit el agarre de la bola (5) vale también contra las paredes; acá las paredes tienen agarre 0, que es lo que declara el material de la pista del kit, para que el vehículo deslice a lo largo en vez de frenarse en seco. Se cambia en WALL.
  • Las paredes de la pista son las formas de colisión del kit, triángulo por triángulo. El piso es el mismo plano de 60 × 60 m. Como en el kit, la bola mide 1 m y el modelo es más largo: la trompa se mete un poco en las paredes.
  • Si el vehículo se cae del mundo vuelve a la largada (en el kit cae para siempre). R y el selector de vehículos son agregados: el kit trae la camioneta amarilla en la escena y la moto en una escena aparte.
  • El kit prende oclusión ambiental, luz indirecta y resplandor (SSAO, SSIL, glow); acá no están. Los sonidos no se atenúan con la distancia.
  • La cámara corta lo que está a más de 60 m, como en el kit.

Créditos y licencia

El kit original, sus modelos, imágenes y sonidos son de Kenney (kenney.nl): el código bajo licencia MIT (va copiada en LICENSE-kenney.md) y los modelos, imágenes y sonidos en dominio público (CC0). El port a three.js es nuestro, también MIT. No hace falta dar crédito a Kenney, pero se agradece.