fogón

← BibliotecaKitmormon/starter-platformer

Kit de inicio: plataformas 3D

m@mormonKit, MIT
v1hace 2 días

Obra original de Kenney ↗

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

Kit de inicio: plataformas 3D

El Starter Kit 3D Platformer de Kenney, que está hecho para Godot, pasado a three.js y TypeScript: el mismo personaje, el mismo nivel y los mismos números, leídos de sus escenas y sus scripts. Un muñeco que camina, salta dos veces y se aplasta al caer; una cámara que lo sigue y gira; monedas, ladrillos que se rompen de un cabezazo, plataformas que se caen y nubes.

El personaje saltando entre plataformas, con monedas y un ladrillo roto

En la demo: W A S D camina, espacio salta (y otra vez en el aire), las flechas giran la cámara y + / − la acercan y la alejan, como en el original. Además se puede arrastrar para girar y usar la rueda. En el teléfono: el pulgar izquierdo camina, un botón salta, otros dos acercan y alejan, y arrastrando se gira. También anda con un mando. Si te caés, el nivel empieza de nuevo.

Es solo la página. No hay sala ni red: todo pasa en el navegador de quien juega. Es una base para empezar un juego de plataformas, no un juego terminado (el original tampoco: la bandera del final no hace nada).

Qué trae

client/platformer es lo que se lleva.

ArchivoQué hace
game.tsGame: todo junto. Arma el nivel, el jugador, la cámara y la luz, avanza la física a 60 pasos por segundo (como Godot) y dibuja. Avisa con onCoins y onRestart.
level.tsLEVEL: el nivel del kit como una lista (qué, dónde, girado cuánto), y los tipos Level y Thing para escribir otro.
player.tsPlayer: el personaje del kit (player.gd). Sus números están a la vista: movementSpeed 250, jumpStrength 7, fallAcceleration 25. Doble salto, se estira al saltar y se aplasta al caer, rastro de polvo y la sombra redonda debajo.
view.tsView: la cámara (view.gd). Sigue al jugador con retraso, gira y se acerca (zoom de 4 a 16 m).
objects.tsWorld: lo que hay en el nivel y lo que hace. Monedas que giran y se juntan, ladrillos que se rompen, plataformas que se caen, nubes que flotan.
physics.tsLos choques: una cápsula contra cajas y cilindros (moveAndSlide, box, cylinder), con pisos, paredes y techos como los cuenta Godot. Sin motor de física.
environment.tsaddEnvironment: el sol con sus sombras, la luz ambiente, el cielo y la curva de tonos «Filmic» de Godot (useFilmicLook), para que se vea como el original.
particles.tsParticles: las chispas de las monedas, el polvo y los pedazos de ladrillo.
controls.tsControls: teclado y mando, y extra para sumar un pad táctil.
audio.tsAudio: los seis sonidos del kit, cada vez un poco más agudos o más graves, y los pasos.
assets.tsloadAssets: baja los modelos, las imágenes y el cielo, y da copias que comparten geometría y material.

Lo demás es la demo y sirve de ejemplo: client/main.ts tiene el contador de monedas, arrastrar para girar, la rueda y el pad táctil.

Cómo se usa en un juego

fogon pieces add starter-platformer

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

import * as THREE from "three";
import { loadAssets } from "./platformer/assets";
import { Audio } from "./platformer/audio";
import { Controls } from "./platformer/controls";
import { Game } from "./platformer/game";

const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
const assets = await loadAssets();
const audio = new Audio();
await audio.load(assets.base); // los sonidos están en la misma carpeta que los modelos
const game = new Game(renderer, assets, { audio });
const controls = new Controls();

game.onCoins = (coins) => (counter.textContent = String(coins));
addEventListener("pointerdown", () => audio.unlock()); // el navegador pide un gesto antes de sonar
addEventListener("keydown", () => audio.unlock());

function resize() {
  renderer.setSize(innerWidth, innerHeight, false);
  game.resize(innerWidth, innerHeight);
}
addEventListener("resize", resize);
resize();

let last = performance.now();
renderer.setAnimationLoop(() => {
  const now = performance.now();
  game.update((now - last) / 1000, controls.read());
  last = now;
  game.render();
});

Para servir los archivos vos (sin depender de la demo), copiá la carpeta public/platformer de este proyecto a tu public/ (son 880 KB) y llamá loadAssets({ base: "./platformer/" }).

Otro nivel. Un nivel es una lista. Las medidas están en metros, con +Y para arriba, y at es donde apoya cada cosa:

import type { Level } from "./platformer/level";

const mine: Level = {
  spawn: [0, 0.5, 0], // donde aparece el jugador
  floor: -10,         // más abajo que esto, el nivel empieza de nuevo
  things: [
    { kind: "platform-large", at: [0, 0, 0] },
    { kind: "coin", at: [1, 0.635, 0] },
    { kind: "brick", at: [-1, 1.986, 0] },            // a 1,5 m sobre el piso: se rompe de un salto
    { kind: "platform-falling", at: [4, 0.4, 0], yaw: 10 },
    { kind: "platform-grass-large-round", at: [9, 1, 0] },
    { kind: "cloud", at: [3, 2, -6], scale: 1.4 },
    { kind: "flag", at: [9, 1.48, 0] },
  ],
};
const game = new Game(renderer, assets, { audio, level: mine });
// o más tarde: game.restart(mine);

Las plataformas miden 2, 3 y 5 m de lado (platform, platform-medium, platform-large) y medio metro de alto; la redonda, 5 m de diámetro. Con un salto se sube 1 m y con dos, hasta 1,9 m; de un salto se cruzan unos 2,3 m y de dos, unos 4 m.

Otros números. game.player.jumpStrength = 9, game.player.movementSpeed = 320, game.view.zoom = 6: son campos comunes, se cambian cuando quieras.

Algo propio en el nivel. game.scene es la escena de three.js y game.world.colliders la lista de lo que frena al jugador: para una pared o un piso tuyos, agregá tu malla a la escena y un box(x, y0, z, mitadX, alto, mitadZ, giro) o un cylinder(x, y0, z, radio, alto) a esa lista (los dos salen de physics.ts; el giro va en radianes). Al reiniciar el nivel la lista se arma de nuevo.

Solo la luz. addEnvironment(renderer, scene, { sky }) sirve sin el resto, para darle a cualquier escena el aspecto del kit. La pieza starter-scene (el kit de la escena básica) es eso mismo, más completo.

Qué quedó distinto del original

  • Los choques son cajas y cilindros. En Godot las plataformas chocan con la forma exacta de su malla (bordes redondeados incluidos); acá, con una caja del mismo tamaño (un cilindro para la redonda). El personaje es la misma cápsula de 0,3 m de radio y 1 m de alto.
  • Sin oscurecimiento en los rincones ni resplandor. El original prende SSAO y «glow»; acá no están. El sol, la luz ambiente, el cielo, la curva de tonos y los ajustes de brillo, contraste y saturación son los mismos, y los colores salen a pocos puntos de los de la captura del kit.
  • Las sombras son las de three.js: un solo mapa de 2048 px que sigue al jugador (16 m para cada lado).
  • Se agregó arrastrar para girar la cámara, la rueda y el pad táctil. El teclado y el mando son los del kit.

Límites

  • Es para uno: no hay sala. server/room.ts y shared.ts están vacíos, solo porque todo proyecto de Fogón los lleva.
  • No hay enemigos, ni vidas, ni final: como en el kit, solo se juntan monedas.
  • La curva de tonos se pone en el lugar del «tone mapping a medida» de three.js, que es uno solo por página.
  • Necesita WebGL 2 (cualquier navegador actual). three.js es una dependencia de npm: el juego que lo use se arma con la CLI de Fogón (fogon dev, fogon deploy).

Créditos y licencias

  • De Kenney (kenney.nl): el diseño del juego, el nivel y sus números (el repositorio original, con licencia MIT: está en LICENSE-kenney.md), y los modelos, las imágenes y los sonidos de public/platformer, en dominio público (CC0).
  • Nuestro: el pase a three.js y TypeScript (todo client/), también MIT.
  • La tipografía del contador es Lilita One, de Google Fonts (como en el kit).