← LibraryKitmormon/starter-racing
Clone
fogon pieces add starter-racinggit clone https://git.fogon.gg/mormon/starter-racing.gitfogon new starter-racing --from starter-racinghttps://fogon.gg/piece/45/kit-de-inicio-carrerasKit 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.

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.
| Archivo | Qué hace |
|---|---|
vehicle.ts | Vehicle 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.ts | Ball (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.ts | El circuito: TRACK (el del kit, escrito como texto), parseTrack, buildTrack (lo dibuja y pone sus paredes en el Collider), PIECES y addMeshCollision. |
walls.ts | Las paredes de la recta y de la curva, como triángulos (las formas de colisión del kit). |
view.ts | View: la cámara que sigue al vehículo y se aleja cuando va rápido (el view.gd del kit). |
trail.ts | Trail: el humo del derrape. |
audio.ts | VehicleSounds: el motor y el chirrido en bucle (cambian de tono y volumen) y el golpe. |
environment.ts | Environment: el fondo, la luz de ambiente, el sol con sombras y la misma curva de color que usa Godot («Filmic»). |
assets.ts | De 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.
Pick a file.
| Version | When | Size | Commit |
|---|---|---|---|
| v1 in the demo | Nov 12, 2026 | 2.5 MB | 47e564f |
Nobody has published a version made from this piece yet.
Put together the request for your agent
Pick what you want to do, copy the request and paste it into your AI (Claude, ChatGPT, Codex…). It does the rest.