fogón

← BibliotecaObjetomormon/weapons

Armas por código

m@mormonObjeto, MIT
v9hace 2 días
Clonar
FOGONfogon pieces add weapons
GITgit clone https://git.fogon.gg/mormon/weapons.git
REMIXfogon new weapons --from weapons
ZIPweapons.zipBajar
LINKhttps://fogon.gg/piece/53/armas-por-codigo
Controles

Armas por código

Un pack de armas modeladas enteras por código (three.js): ningún archivo de modelo, ninguna imagen. Cada arma es una función que la arma en el momento, con medidas reales, sus materiales generados (acero pavonado, chapa pintada, madera laminada, polímero), el desgaste en los cantos y el oscurecido de los rincones. Las partes se mueven, y todas cuelgan de la cámara apuntando a donde mira.

Hoy trae trece cosas (nueve armas de fuego, un cuchillo y tres granadas), y se van a ir sumando más:

El rifle de cerrojo con su mira, la escopeta, la ametralladora ligera, el fusil, la carabina, la recortada, el subfusil, la pistola, el revólver, el cuchillo y las tres granadas colgados en la armería, a la misma escala

Son tipos genéricos: un fusil de la familia AK, una carabina de la familia AR, una pistola de servicio, un subfusil compacto, una ametralladora ligera de cinta, una escopeta de dos caños (entera y recortada), un rifle de cerrojo con mira telescópica, un revólver de doble acción, un cuchillo de combate y tres granadas de mano (fragmentación, humo y aturdidora) como tantos. No copian un modelo real, no llevan marcas de fábrica y los rótulos y números que se ven son inventados.

En la demo: la lista de armas a un costado (y «Todas juntas»: la armería, un tablero donde cuelgan todas a la misma escala, de perfil, acomodadas solas en filas según el largo de cada una, las cosas chicas de un mismo tipo juntas; las que se sumen al catálogo encuentran su lugar). Para la elegida: cinco vistas (la última, En la mano, es como la ve el jugador), un botón por cada parte que se mueve, sus acabados, el desgaste, los colores (un panel con cada zona del arma: diez colores rápidos, un selector libre y «Restablecer»), sus medidas y triángulos, la línea de código que la crea y la descarga del .glb tal como está en pantalla. Se gira arrastrando y la rueda (o dos dedos) acerca. Nada pasa por una sala: es solo la página.

Qué trae

Una carpeta, client/weapons (fogon pieces add weapons):

ArchivoQué hay
index.tsEl catálogo. WEAPONS (cada arma con su nombre, tipo, opciones, partes que se mueven, cómo se sostiene y sus archivos), WEAPON_LIST, y createWeapon(id, options).
ak47.tscreateAk47(options): el fusil.
pistol.tscreatePistol(options): la pistola.
smg.tscreateSmg(options): el subfusil.
m4.tscreateM4(options): la carabina.
lmg.tscreateLmg(options): la ametralladora ligera.
shotgun.tscreateShotgun(options): la escopeta de dos caños y, con variant: "sawed", la recortada.
rifle.tscreateBoltRifle(options): el rifle de cerrojo con su mira telescópica.
revolver.tscreateRevolver(options): el revólver.
knife.tscreateKnife(options): el cuchillo de combate.
grenades.tscreateGrenade({ kind }): las tres granadas ("frag", "smoke", "flash").
kit.tsEl kit de modelado, que no sabe nada de armas: contornos extruidos con cantos biselados, piezas torneadas, alambres, cuerpos de sección variable (loft), cuerpos cuyo contorno cambia esquina por esquina a lo largo (sweep: una hoja con su filo, su vaceo y su contrafilo), chapa con relieve (sheet), tornillos, ranuras, y Model, que junta todo, oscurece los rincones y pela los cantos.
textures.tsLos materiales, pintados en un canvas: acero, chapa pintada, madera laminada y polímero (color, aspereza/metal y relieve), y el atlas de rótulos.
studio.tsstudio(scene, renderer): un entorno para que el acero tenga qué reflejar, y las luces.
glb.tstoGlb(object): el arma construida, como archivo .glb.

Las armas

idQué esMedidas (m)TriángulosOpcionesPartes (pose)
ak47Fusil tipo AK-47: cajón estampado con rebajes y remaches, tapa acanalada, culata redondeada con cantonera, cargador curvo de 30, freno de boca oblicuo, baqueta0,882 × 0,260 × 0,069 · cañón 0,41535.886 (madera) · 39.734 (polímero)furniture: "wood" | "polymer"bolt 0…1 · magazine 0…1 · trigger 0…1 · selector 0, 1, 2
pistolPistola semiautomática: corredera con estrías y ventana de expulsión con fondo, armazón con riel, martillo de anillo, cachas cuadrilladas, cargador con testigos0,199 × 0,147 × 0,034 · cañón 0,11619.929finish: "black" | "two-tone" | "steel" · grips: "polymer" | "wood"slide 0…1 · magazine 0…1 · trigger 0…1 · hammer 0…1 · safety 0…1
smgSubfusil compacto: cajón de chapa con rebajes, ventana de expulsión con el cerrojo adentro, palanca de carga en su ranura, riel corto, alza de anillo y punto de mira con túnel, culata de esqueleto que se pliega a la izquierda sobre su bisagra, empuñadura cuadrillada, cargador recto de 30 con nervaduras y testigos, guardamanos ventilado, apagallamas de tres puntas0,484 × 0,232 × 0,054 (plegado: 0,32 de largo) · cañón 0,17927.907finish: "black" | "steel" | "sand"stock 0…1 · bolt 0…1 · magazine 0…1 · trigger 0…1 · selector 0, 1, 2
m4Carabina tipo M4: cajón superior con riel, ventana de expulsión con el cerrojo adentro y tapa guardapolvo con bisagra, botón de empuje y deflector, cajón inferior con brocal de cargador, pasadores y retenes, alza y punto de mira en torre triangular, guardamanos de cuatro rieles (o redondo con nervaduras), cañón escalonado con apagallamas de jaula, tubo con tuerca almenada y culata que corre sobre él, empuñadura cuadrillada, cargador curvo de 30 con nervaduras0,842 × 0,260 × 0,054 (culata recogida: 0,76 de largo) · cañón 0,36838.157 (rieles) · 35.237 (redondo)finish: "black" | "sand" | "two-tone" · handguard: "rails" | "smooth"stock 0…1 · bolt 0…1 (palanca de carga) · magazine 0…1 · trigger 0…1 · selector 0, 1, 2 · dustCover 0 abierta … 1 cerrada
lmgAmetralladora ligera: cajón largo de chapa con rebajes, puntos de soldadura y remaches, ventana de expulsión con el cerrojo adentro, palanca de carga en su ranura, tapa de alimentación con bisagra y pestillos que se abre sobre la bandeja, riel corto y alza con orejas sobre la tapa, cinta de cartuchos y eslabones (en geometría) que sube por el flanco izquierdo desde una caja de 200 o un tambor, cañón pesado con manija de transporte rebatible, protector de calor, tubo de gases con regulador, punto de mira con orejas y apagallamas de jaula, guardamanos ventilado, bípode de dos patas que se pliega, empuñadura cuadrillada, seguro de botón y culata maciza o de esqueleto con cantonera y apoyo de hombro1,087 × 0,224 × 0,091 (con tambor: 0,255 de alto; bípode abajo: 0,36) · cañón 0,49043.768 (tambor y esqueleto: 44.156)feed: "belt" | "drum" · finish: "black" | "sand" | "olive" · stock: "solid" | "skeleton"bipod 0…1 (nodos bipodLeft y bipodRight) · cover 0…1 · bolt 0…1 · magazine 0…1 · trigger 0…1 · carryHandle 0…1
shotgunEscopeta de dos caños yuxtapuestos del 12, de quebrar: caños que convergen hacia las bocas (huecas), banda superior mateada con punto de mira de perla, báscula con bolas, paneles, pasadores y tornillos con cabeza, llave de apertura, seguro corredizo en la espiga, dos gatillos en su guardamonte, guardamanos con su hierro y su pestillo, culata de pistolete con cuadrillado y cantonera, anillas de correa. Quebrada deja ver las recámaras, el extractor y los dos cartuchos1,127 × 0,187 × 0,057 · caños 0,71029.450finish: "blued" | "steel" · wood: "walnut" | "light"barrels 0 cerrada … 1 quebrada · shells 0 en las recámaras … 1 afuera · lever 0…1 · triggerFront 0…1 · triggerRear 0…1 · safety 0…1
sawed-offEscopeta recortada: la misma (createShotgun({ variant: "sawed" })) con los caños aserrados a 28 cm (acero vivo en el corte, sin punto de mira) y la culata cortada detrás de la mano: madera clara con las marcas del serrucho y unas vueltas de cinta0,490 × 0,121 × 0,057 · caños 0,28025.594finish: "blued" | "steel" · wood: "walnut" | "light"barrels 0 cerrada … 1 quebrada · shells 0 en las recámaras … 1 afuera · lever 0…1 · triggerFront 0…1 · triggerRear 0…1 · safety 0…1
bolt-rifleRifle de cerrojo con mira: cajón redondo con ventana de expulsión (con el cerrojo atrás se ve el cartucho sobre el cargador), cerrojo pulido con manija de bola que se levanta y corre, nuez con seguro de aleta, riel de 19 dientes, mira telescópica en dos anillas (tubo, campanas, anillo de aumentos moleteado, tres torretas con sus marcas y diales, lentes tratadas que reflejan), cañón cónico con corona rebajada y collar moleteado, cargador de petaca, guardamonte con sus tornillos, culata de una pieza (nogal o polímero) con carrillera, cuadrillado en la garganta y el guardamanos, cantonera de goma y dos anillas portafusil1,133 × 0,243 × 0,086 · cañón 0,62038.527 (con mira) · 28.455 (sin mira)stock: "wood" | "polymer" · optic: "scope" | "none" · finish: "blued" | "steel"bolt 0 cerrado … 0,35 manija arriba … 1 atrás · magazine 0…1 · trigger 0…1 · safety 0…1 · scope 0 puesta … 1 quitada (se oculta)
revolverRevólver de doble acción y seis tiros: armazón con ventana y cantos redondeados, tambor acanalado con las seis recámaras huecas y sus muescas, grulla que lo abre a la izquierda, estrella y varilla eyectora con punta moleteada, seis cartuchos con su fulminante, cañón con banda mateada y punto de mira en rampa con inserto rojo, alza regulable, martillo con espolón estriado, pestillo, tapa lateral con tornillos, cachas cuadrilladas con medallón0,235 × 0,143 × 0,038 · cañón 0,102 (corto 0,064 · largo 0,152)18.330finish: "blued" | "steel" | "nickel" · grips: "wood" | "rubber" · barrel: "short" | "medium" | "long"crane 0 cerrada … 1 abierta · cylinder 0…1 (una vuelta) · ejector 0…1 · rounds 0 puestos … 1 afuera (se ocultan) · hammer 0…1 · trigger 0…1
knifeCuchillo de combate: hoja de 18 cm con filo de verdad (sección en cuña que se afina hacia la punta), punta recortada con contrafilo, vaceo, dientes de sierra en el lomo, bisel afilado más brillante que los planos, guarda de acero, mango ovalado con ranuras (o encordado) y pomo con agujero para el fiador0,307 × 0,057 × 0,025 · hoja 0,1806.246 (con sierra) · 5.818 (liso)blade: "saw" | "plain" · finish: "steel" | "black" · handle: "polymer" | "cord"no tiene (puntos tip, grip, guard)
fragGranada de fragmentación: cuerpo de fundición en forma de huevo cortado en segmentos por surcos de verdad (o esfera lisa con su costura y su rótulo), franja amarilla, espoleta roscada con su cabeza, palanca de chapa con nervio que sigue el cuerpo, pasador partido con anilla0,062 × 0,120 × 0,057 (lisa: 0,068 × 0,095 × 0,065)6.356 · 5.046 (lisa)shell: "segmented" | "smooth"pin 0 puesto … 1 sacado (se oculta) · lever 0 sujeta … 1 suelta (se oculta)
smokeGranada de humo: bote con los bordes engatillados, cuatro orificios arriba y uno en la base, franja del color del humo, rótulo inventado alrededor; la misma espoleta, palanca y anilla0,059 × 0,130 × 0,0566.140band: "yellow" | "red" | "green" | "violet"pin 0…1 · lever 0…1
flashGranada aturdidora: tubo de acero con dos hileras de seis orificios con su reborde entre dos cabezas hexagonales, franja azul, rótulo inventado; la misma espoleta, palanca y anilla0,050 × 0,128 × 0,0557.302—pin 0…1 · lever 0…1

Las tres granadas salen de createGrenade({ kind }); en el catálogo son tres entradas (createWeapon("smoke")).

Todas aceptan además wear (0 nueva … 1 muy gastada), occlusion (cuánto se oscurecen los rincones), textureSize (1024 por defecto; 512 arma más rápido y alcanza en un teléfono), seed y materials (para compartir los materiales entre varias). Construir una lleva unos 0,7 s con texturas de 1024 y 0,3 s con 512, y ocupa el hilo mientras tanto: hacelo al cargar, no en medio de la partida.

Colores

Todas aceptan también colors: un color por zona del arma, encima del acabado que tenga. Cada zona recibe "#rrggbb", un número (0x56603a) o un THREE.Color; la que no se nombra queda como viene.

const rifle = createWeapon("m4", { colors: { metal: "#56603a", furniture: "#b39a6b", accent: 0x1b1c1f } });

rifle.setColors({ metal: "#a32a22" });      // cambia esa zona en el momento; las demás quedan como estaban
rifle.setColors({ metal: null });           // esa zona vuelve a su color de fábrica

Es un teñido del color base, no una mano de pintura lisa: el desgaste de los cantos, el oscurecido de los rincones, los rebajes más oscuros, la veta de la madera, la aspereza y el relieve siguen ahí. setColors no reconstruye nada (reescribe los colores de los vértices, cerca de un milisegundo), así que sirve para un selector en vivo. El color es de esa arma: dos que comparten materials se tiñen cada una por su lado. Y toGlb(gun.object) lo lleva al archivo.

idZonas
ak47metal Metal (cajón, tapa, cañón) · furniture Culata y guardamanos · accent Cargador
pistolmetal Corredera · frame Armazón · furniture Cachas · accent Controles
smgmetal Cajón y culata · furniture Armazón y empuñadura · accent Cargador
m4metal Cajones y guardamanos · furniture Culata y empuñadura · accent Cargador
lmgmetal Cajón y tapa · furniture Culata, guardamanos y empuñadura · accent Caja o tambor
shotgun, sawed-offmetal Acero (báscula, caños, guardamonte) · furniture Madera · accent Cartuchos
bolt-riflemetal Acero (cajón, cañón, guardamonte) · furniture Culata · accent Mira · magazine Cargador
revolvermetal Armazón y cañón · cylinder Tambor · furniture Cachas · accent Martillo y gatillo
knifemetal Hoja (el bisel afilado queda de acero) · furniture Mango · accent Guarda y pomo
frag, smoke, flashbody Cuerpo · band Franja · fuze Espoleta y palanca

El catálogo las trae con su nombre: WEAPONS.pistol.zones → [{ key: "metal", name: "Corredera" }, …]. Lo que no está en ninguna zona (cerrojo, miras, pasadores, tornillos) conserva su color. Para tener en cuenta: el acero y la chapa siguen siendo metal, así que un color muy claro se ve como metal brillante más que como pintura blanca, y la madera teñida sale algo más clara que el color pedido porque la veta se le suma.

Cómo se usa en un juego

Todo está en metros. Cada arma apunta a −Z con +Y hacia arriba, el ánima sobre el eje z y el origen sobre el gatillo: colgada de una cámara, apunta a donde la cámara mira. El cuchillo apunta igual (la punta a −Z, el filo hacia abajo) con el origen en el medio del mango; las granadas están de pie (+Y), con el origen en el medio del cuerpo, la palanca del lado +Z y la anilla del lado +X. El hold de esos cuatro es para tenerlos en la mano, no para apuntar.

Una en la mano:

import { createWeapon, WEAPONS } from "./weapons";
import { studio } from "./weapons/studio";

studio(scene, renderer);                    // sin un entorno que reflejar, el acero sale negro

const gun = createWeapon("pistol", { finish: "two-tone", grips: "wood" });
const { at, turn } = WEAPONS.pistol.hold;   // dónde queda bien delante de la cámara
gun.object.position.set(...at);
gun.object.rotation.set(...turn);
camera.add(gun.object);
scene.add(camera);

createWeapon("ak47", …) devuelve lo mismo que createAk47(…), y createWeapon("pistol", …) lo mismo que createPistol(…): { object, parts, materials, info, points, pose(), setColors(), dispose() }.

Mover las partes. pose() recibe las que quieras cambiar; cada una gira o corre sobre su eje real:

gun.pose({ slide: 1 });                     // corredera atrás: asoma el cañón y se monta el martillo
gun.pose({ trigger: 0.6, safety: 1 });

// un disparo: la corredera va y vuelve en una décima de segundo
let kick = 0;
function fire() { kick = 1; }
function frame(dt: number) {
  kick = Math.max(0, kick - dt * 10);
  gun.pose({ slide: Math.sin(kick * Math.PI) });
}

Para animarlas vos, gun.parts.slide, gun.parts.magazine, etc. son nodos aparte; y el catálogo dice qué hay en cada arma: WEAPONS.ak47.parts → [{ key: "bolt", name: "Cerrojo", max: 1, about, speed }, …].

Dónde sale el fogonazo y por dónde salta la vaina. points da posiciones en el espacio del arma:

const muzzle = new THREE.Vector3(...gun.points.muzzle);
gun.object.localToWorld(muzzle);            // la boca del cañón, en el mundo: ahí va el fogonazo y nace la trazadora
const eject = gun.object.localToWorld(new THREE.Vector3(...gun.points.eject));   // la ventana de expulsión

También están rearSight, frontSight (la línea de miras, para apuntar por ellas), grip y, en el fusil, handguard (dónde van las manos). El rifle de cerrojo tiene scopeEye (dónde va el ojo para mirar por la mira); el cuchillo, tip, grip y guard; las granadas, center y top.

Tirar una granada. El pasador sale, la palanca salta y lo que vuela es el mismo objeto:

const grenade = createWeapon("frag");
grenade.pose({ pin: 1 });                   // sacó el seguro: la anilla y el pasador ya no están
grenade.pose({ lever: 0.5 });               // la palanca se abre… y en 1 ya voló (se oculta)

Varias armas, mismos materiales. Las texturas son lo más caro; se pueden hacer una vez y compartir (el wear y el seed quedan en los materiales):

import { createMaterials } from "./weapons";
const materials = createMaterials({ size: 512, wear: 0.6 });
const rifle = createWeapon("ak47", { materials });
const sidearm = createWeapon("pistol", { materials });

Al terminar, gun.dispose() libera su geometría y sus texturas. Los materiales compartidos no los toca: esos se liberan aparte, una vez, con materials.dispose().

Como archivo .glb

Si preferís cargar un archivo en vez de construir el arma (otro motor, un programa de modelado, o para no ocupar el hilo), la demo sirve estos, con CORS:

ArchivoQué esPeso
ak47.glbEl fusil, madera1,2 MB
ak47-polymer.glbEl fusil, polímero1,6 MB
pistol.glbLa pistola negra, cachas de polímero1,3 MB
pistol-two-tone-wood.glbLa pistola de dos tonos, cachas de madera1,5 MB
smg.glbEl subfusil negro1,4 MB
smg-sand.glbEl subfusil arena1,5 MB
m4.glbLa carabina negra, guardamanos de rieles1,6 MB
m4-two-tone-smooth.glbLa carabina de dos tonos, guardamanos redondo1,6 MB
lmg.glbLa ametralladora negra, caja y cinta, culata maciza1,7 MB
lmg-drum.glbLa ametralladora verde oliva, tambor, culata de esqueleto1,7 MB
shotgun.glbLa escopeta pavonada, madera de nogal1,6 MB
shotgun-steel-light.glbLa escopeta con báscula de acero y madera clara1,6 MB
sawed-off.glbLa recortada1,5 MB
bolt-rifle.glbEl rifle de cerrojo, nogal, con mira1,7 MB
bolt-rifle-polymer.glbEl rifle de cerrojo, culata de polímero1,6 MB
revolver.glbEl revólver pavonado, cachas de madera, cañón medio1,0 MB
knife.glbEl cuchillo, hoja de acero con sierra0,6 MB
frag.glbLa granada de fragmentación, segmentada0,6 MB
smoke.glbLa granada de humo, franja amarilla0,7 MB
flash.glbLa granada aturdidora0,7 MB
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { MeshoptDecoder } from "three/examples/jsm/libs/meshopt_decoder.module.js";

const loader = new GLTFLoader().setMeshoptDecoder(MeshoptDecoder);   // sin esta línea no abren
const gltf = await loader.loadAsync("https://play.fogon.page/weapons/pistol.glb");
const slide = gltf.scene.getObjectByName("slide")!;   // las partes siguen siendo nodos, con su eje
camera.add(gltf.scene);

Estos archivos están comprimidos para que pesen un tercio (los 18 juntos, 23 MB en vez de 69): las imágenes van en WebP y la geometría empaquetada con meshopt. Usan tres extensiones de glTF: EXT_texture_webp y KHR_mesh_quantization, que three.js carga solo, y EXT_meshopt_compression, que necesita el decodificador de arriba (viene con three.js). Otros programas pueden no abrirlos: para Blender, otro motor o un visor cualquiera, usá el archivo sin comprimir, que se explica abajo. La forma, los triángulos, los nodos de las partes y los colores por vértice son los mismos que los del arma construida; de muy cerca el grano fino del acero y del polímero se ve un poco más suave.

El archivo sin comprimir (glTF 2.0 común, sin ninguna extensión: imágenes JPEG y PNG, de 1,6 a 5 MB por arma) es el que entrega el botón Descargar .glb de la demo, y el que da toGlb(gun.object); por ahí sale también cualquier otra combinación (acero, otro desgaste, tus colores). En el archivo las partes se mueven a mano (no viene pose()).

tools/export.ts escribe los dos: el común en .raw/ y el comprimido en public/ (tools/squeeze.ts), y tools/compare.ts carga cada archivo al lado del arma construida y compara triángulos y partes.

Agregar un arma

  1. Un archivo client/weapons/<arma>.ts que exporte create<Arma>(options) y sus tipos <Arma>Options y <Arma>Pose, armado solo con kit.ts y textures.ts (si al kit le falta una herramienta, va en kit.ts, sin nada propio de esa arma). pistol.ts es el ejemplo más corto: unas 470 líneas.
  2. La convención: metros y medidas reales; apunta a −Z, +Y arriba, ánima sobre el eje z, origen sobre el gatillo; devuelve { object, parts, materials, info, points, pose(), dispose() } con points.muzzle, eject, rearSight, frontSight y grip; cada parte que se mueve es un nodo con nombre y su pivote real; wear, occlusion, textureSize, seed, materials y colors como opciones comunes. Para colors: un ZONES que diga qué acabados (Finish) forman cada zona (metal, furniture, accent como mínimo), pasado a m.build({ zones }), y setColors() en lo que devuelve (ver pistol.ts).
  3. **Una entrada en WEAPONS** (index.ts): nombre, tipo, opciones con sus valores, partes, zonas (zones, con su nombre), hold (dónde cuelga de la cámara), files y, si lo que mide info.barrel no es un cañón, measure ("hoja", o null si no hay nada que medir). Con eso aparece sola en la demo; bun tools/export.ts <id> escribe sus .glb.

La vara de calidad, que se exige desde el primer intento:

  • Ninguna chapa lisa: líneas de panel, rebajes y escalones de verdad, con profundidad.
  • Secciones redondeadas donde agarra la mano (loft), no tablas.
  • El relieve es geometría (estrías, cuadrillado, nervaduras), no un dibujo.
  • Tornillos, pasadores y remaches con cabeza.
  • Rótulos legibles e inventados: sin marcas ni números de serie reales.
  • Rincones oscurecidos y desgaste que siga los cantos y las zonas de roce, sin manchas repetidas.
  • Madera laminada por posición y sobria; polímero que se vea negro, no gris.
  • Se diseña mirando la vista En la mano: es la que ve el jugador.
  • Presupuesto: 12–20 mil triángulos un arma corta, 25–40 mil una larga (hasta 45 mil una con cinta de cartuchos, como la ametralladora), 4–12 mil un cuchillo o una granada.
  • Se revisa en capturas (perfil, tres cuartos, primer plano, en la mano) al lado de las que ya están, misma luz.

Licencia

MIT: el código y lo que el código dibuja. Todo es nuestro; no hay modelos, imágenes ni marcas de terceros.