← LibraryKitmormon/starter-fps
Clone
fogon pieces add starter-fpsgit clone https://git.fogon.gg/mormon/starter-fps.gitfogon new starter-fps --from starter-fpshttps://fogon.gg/piece/44/kit-de-inicio-disparos-en-primera-personKit de inicio: disparos en primera persona
El Starter Kit FPS de Kenney, que es un proyecto de Godot, pasado a TypeScript y three.js para Fogón: la misma mecánica, los mismos números y el mismo nivel. Un personaje que camina, salta dos veces y dispara; dos armas con su cadencia, su dispersión y su retroceso; enemigos voladores que te apuntan y te disparan de cerca; y unas islas flotando en el cielo.

En la demo: W A S D para moverse, espacio para saltar (se puede una segunda vez en el aire), clic para disparar y E (o el botón del medio) para cambiar de arma. El mouse queda adentro del juego; Esc lo suelta. Si el navegador no deja atraparlo, se mira arrastrando. En el teléfono: una palanca a la izquierda, se mira arrastrando el resto de la pantalla, y tres botones. También anda con un mando. Si te caés o te quedás sin vida, el nivel empieza de nuevo.
Es solo la página. No hay sala ni red: todo pasa en el navegador de cada uno. server/room.ts es la sala vacía que Fogón le pide a cualquier proyecto.
Qué es de quién
- El kit original (el código en GDScript y el armado del nivel) es de Kenney, con licencia MIT: su texto está en [
LICENSE-kenney.md](LICENSE-kenney.md). - Los modelos, los sprites y los sonidos (
public/fps/) son de Kenney y están en dominio público (CC0). - El port a TypeScript es nuestro, también MIT. La fuente del marcador es Lilita One, que se carga de Google Fonts.
Los archivos
Todo lo que se reusa está en client/fps/. client/main.ts es solo la demo: lee el teclado, el mouse, los dedos y el mando, y dibuja el marcador.
| Archivo | Qué tiene | En el kit de Godot |
|---|---|---|
game.ts | Game: arma el nivel, el jugador, los enemigos y las nubes; update(dt, input) y render(renderer) | scenes/main.tscn |
player.ts | Player y PLAYER (velocidad, saltos, gravedad, tamaño): caminar, saltar, mirar, disparar, el retroceso, cambiar de arma | objects/player.gd, player.tscn |
weapons.ts | Weapon, BLASTER, REPEATER, WEAPONS | scripts/weapon.gd, weapons/*.tres |
enemy.ts | Enemy y ENEMY (vida, daño, alcance, cada cuánto dispara) | objects/enemy.gd, enemy.tscn |
level.ts | LEVEL (el nivel como datos), buildLevel, buildSun, Cloud, at() | scenes/main.tscn, objects/cloud.gd |
collide.ts | Solids: los triángulos del nivel, un rayo contra ellos y un cuerpo que resbala por ellos | lo hace el motor de Godot |
sprites.ts | Flash: un sprite que pasa unos cuadros y se apaga (el fogonazo, la marca del disparo) | AnimatedSprite3D, objects/impact.gd |
audio.ts | Sounds: la cola de sonidos y los pasos | scripts/audio.gd |
look.ts | applyLook(renderer), el cielo y la luz de ambiente | scenes/main-environment.tres |
assets.ts | loadAssets(), model(): baja los modelos, sprites y sonidos | la carpeta del proyecto |
index.ts | exporta todo lo anterior |
Cómo se usa en un juego
fogon pieces add starter-fps
Eso trae client/fps/ y agrega three.js al package.json. Los modelos, sprites y sonidos (menos de 1 MB) no viajan con el juego: se cargan de la demo publicada de esta pieza (https://play.fogon.page/starter-fps/fps/).
import * as THREE from "three";
import { applyLook, Game, loadAssets, noInput } from "./fps";
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
applyLook(renderer); // sombras y el mismo tono que en Godot
const game = new Game(await loadAssets());
game.onHealth = (health) => (label.textContent = `${health}%`);
game.onWeapon = (weapon) => (crosshair.src = `${game.assets.base}sprites/${weapon.crosshair}`);
game.onEnemyDestroyed = (left) => console.log(`quedan ${left}`);
game.resize(innerWidth / innerHeight);
// Los sonidos arrancan con el primer gesto de la persona.
addEventListener("pointerdown", () => game.sounds?.unlock(), { once: true });
let last = performance.now();
renderer.setAnimationLoop((now) => {
const input = noInput();
input.moveY = keys.has("KeyW") ? -1 : 0; // adelante es −1
input.lookX = mouseX; // píxeles que se movió el mouse desde el cuadro anterior
input.lookY = mouseY;
input.shoot = mouseDown; // mientras esté apretado
input.jump = spacePressed; // solo el cuadro en que se apretó
input.toggle = ePressed;
game.update((now - last) / 1000, input);
game.render(renderer);
last = now;
});
client/main.ts tiene eso mismo completo, con mouse atrapado, dedos y mando. Para servir los archivos desde tu juego, copiá public/fps/ de esta pieza a tu public/ y cargalos con loadAssets({ base: "./fps/" }).
Otra arma
Un arma son números. Copiá una y cambiale lo que quieras:
import { BLASTER, Game, WEAPONS, type Weapon } from "./fps";
const cannon: Weapon = { ...BLASTER, cooldown: 0.8, shotCount: 5, spread: 2, damage: 40, knockback: 50 };
const game = new Game(assets, { weapons: [...WEAPONS, cannon] });
cooldown son los segundos entre disparos, shotCount los rayos por disparo, spread cuánto se abren (en metros, al final de su alcance), maxDistance el alcance, damage lo que saca cada rayo, knockback cuánto te empuja para atrás y minKnockback/maxKnockback cuánto salta la vista (radianes: arriba, al costado).
Otro nivel
LEVEL es una lista de piezas, cada una con su modelo y su lugar. Los lugares están escritos como los escribe Godot en un .tscn (Transform3D(…): doce números), así que un nivel armado en el editor de Godot se copia tal cual; para escribirlos a mano, at(x, y, z, giro):
import { at, Game, LEVEL, type Level } from "./fps";
const level: Level = {
...LEVEL,
start: [0, 0.5, 0],
pieces: [
{ model: "platform-large-grass", transform: at(0, -0.5, 0) },
{ model: "platform", transform: at(4, 0.5, -3, 45) },
{ model: "wall-high", transform: at(-1, 0, -2) },
],
enemies: [[4, 3, -3]],
clouds: [],
};
const game = new Game(assets, { level });
Todo lo de pieces es sólido (se pisa y frena los disparos) salvo que diga solid: false. Los modelos que hay: platform, platform-large-grass, wall-low, wall-high, grass, grass-small, cloud, enemy-flying, blaster y blaster-repeater. Para usar otros, agregalos a la escena (game.scene.add(...)) y, si tienen que ser sólidos, a game.solids.add(objeto).
Los números del jugador y de los enemigos
PLAYER y ENEMY son objetos comunes: PLAYER.speed = 7, PLAYER.jumps = 3, ENEMY.reach = 8, antes de crear el Game.
Lo que quedó distinto del original
- Sin brillo ni oclusión ambiental. El kit prende el glow y el SSAO de Godot; acá no están. El tono (el «Filmic» de Godot), el cielo, la luz de ambiente y la sombra del sol sí.
- El retroceso no depende de los cuadros por segundo. En el kit el empujón del arma dura un cuadro, así que empuja más cuanto más lento anda el juego; acá empuja siempre lo que empuja a 60 cuadros por segundo. Lo mismo el giro con el mando.
- Las colisiones son propias: el cuerpo son tres esferas que se empujan fuera de los triángulos del nivel. Alcanza para pisos, paredes y bordes; no es un motor de física.
- La mira va en el centro exacto. En el kit queda corrida unos píxeles por cómo está armado el marcador.
- El cielo es el mismo, pasado de PNG (3,9 MB) a JPG (0,4 MB).
Límites
- Un solo jugador. Para hacerlo entre varios hace falta una sala que decida quién le pegó a quién.
- Los enemigos no se mueven de su lugar ni vuelven: cuando no queda ninguno, no pasa nada más. Es una base.
- Los sonidos son
.ogg, como en el kit: los Safari viejos no los reproducen, y ahí el juego anda mudo.
Licencia
MIT (el código, de Kenney y nuestro). Modelos, sprites y sonidos: CC0, de Kenney.
Pick a file.
| Version | When | Size | Commit |
|---|---|---|---|
| v1 in the demo | Nov 12, 2026 | 1.7 MB | f141bb1 |
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.