← MedievalObjeto 3Dfogon/muneco-de-entrenamiento
Muñeco de entrenamiento
Arrastrá para girarlo// «Muñeco de entrenamiento» · gpt-6-sol (medium)
// A medieval training dummy: a wooden post in a base of crossed planks, a straw-stuffed burlap body tied with ropes, a sack head with a painted face (two X eyes), and a wooden crossbar arm sticking out on each side, with a small dented pot as a helmet.
const woodDark = "#493020";
const wood = "#68452b";
const woodLight = "#856039";
const iron = "#353638";
const rope = "#b89a60";
const ropeLight = "#ddc38a";
const burlap = ["#89734e", "#9c855d", "#b29a6c", "#c5ab79"];
const sack = ["#a18b62", "#b7a076", "#cbb48a", "#dac49b"];
function timber(x0, y0, z0, x1, y1, z1) {
box(x0, y0, z0, x1, y1, z1, wood);
box(x0, y0, z0, x1, y1, z0, woodDark);
box(x0, y0, z1, x1, y1, z1, woodLight);
for (let z = z0 + 1; z < z1; z++) {
for (let x = x0; x <= x1; x++) {
if (rand() < 0.045) set(x, y0, z, rand() < 0.5 ? woodDark : woodLight);
}
}
}
// Heavy, crossed feet with squared-off, hand-cut ends.
timber(6, 21, 0, 42, 27, 3);
timber(21, 6, 0, 27, 42, 3);
box(8, 21, 4, 40, 27, 4, woodLight);
box(21, 8, 4, 27, 40, 4, woodLight);
box(10, 21, 4, 11, 27, 4, iron);
box(37, 21, 4, 38, 27, 4, iron);
box(21, 10, 4, 27, 11, 4, iron);
box(21, 37, 4, 27, 38, 4, iron);
for (const x of [10, 38]) {
set(x, 20, 3, "#9c9a8a");
set(x, 28, 3, "#9c9a8a");
}
for (const y of [10, 38]) {
set(20, y, 3, "#9c9a8a");
set(28, y, 3, "#9c9a8a");
}
// Fixed central post, with visible grain and iron collars.
box(21, 21, 4, 27, 27, 18, wood);
box(21, 21, 4, 27, 21, 18, woodDark);
box(22, 21, 5, 22, 21, 17, woodLight);
box(26, 21, 7, 26, 21, 16, "#754e30");
for (const z of [7, 16]) {
box(20, 20, z, 28, 28, z + 1, iron);
box(21, 19, z, 27, 19, z, "#60605d");
for (const x of [22, 26]) set(x, 19, z + 1, "#a7a18c");
}
box(20, 20, 18, 28, 28, 19, woodDark);
// Everything above this joint turns together.
part("upper dummy", [24, 24, 18]);
// Timber crossbar, deliberately behind the stuffed torso.
box(4, 22, 27, 44, 26, 29, woodDark);
box(5, 22, 29, 43, 26, 29, woodLight);
box(5, 22, 28, 43, 22, 28, wood);
for (let x = 6; x <= 42; x++) {
if (rand() < 0.12) set(x, 22, 28, rand() < 0.5 ? woodLight : "#51331f");
}
for (const x of [8, 39]) {
box(x, 21, 27, x + 2, 27, 29, rope);
box(x, 21, 29, x + 2, 27, 29, ropeLight);
set(x + 1, 20, 28, "#e5cb92");
}
for (const x of [5, 43]) {
set(x, 22, 28, "#a5793e");
set(x, 25, 29, "#c3a15d");
}
// Straw escaping from the shoulders and underside.
for (let i = 0; i < 34; i++) {
const side = i < 17 ? -1 : 1;
const x = 24 + side * (7 + Math.floor(rand() * 4));
const y = 21 + Math.floor(rand() * 7);
const z = 25 + Math.floor(rand() * 7);
line(x, y, z, x + side * (1 + Math.floor(rand() * 2)), y, z + Math.floor(rand() * 3) - 1, 0,
rand() < 0.5 ? "#d9b763" : "#aa873c");
}
for (let i = 0; i < 15; i++) {
const x = 20 + Math.floor(rand() * 9);
const y = 21 + Math.floor(rand() * 7);
line(x, y, 19, x + Math.floor(rand() * 3) - 1, y, 17 + Math.floor(rand() * 2), 0, "#c9a754");
}
// Slightly uneven, solid burlap sack.
for (let z = 18; z <= 34; z++) {
for (let y = 17; y <= 30; y++) {
for (let x = 15; x <= 33; x++) {
const dx = (x - 24) / 8.3;
const dy = (y - 24) / 6.1;
const dz = (z - 26) / 8.3;
if (dx * dx + dy * dy + dz * dz <= 1 + (rand() - 0.5) * 0.055) {
let shade = y <= 20 ? 2 : y >= 27 ? 0 : 1;
if (z >= 29) shade++;
if (z <= 21) shade--;
if (rand() < 0.16) shade += rand() < 0.5 ? -1 : 1;
set(x, y, z, burlap[Math.max(0, Math.min(3, shade))]);
}
}
}
}
// Return the front of a sack at a particular x and z.
function faceY(x, z, palette) {
for (let y = 15; y <= 30; y++) {
if (palette.includes(get(x, y, z))) return y - 1;
}
return null;
}
function onFace(x, z, color, palette) {
const y = faceY(x, z, palette);
if (y !== null) set(x, y, z, color);
}
// Broad rope belts, crossed bindings, and a chunky front knot.
for (const z of [21, 22, 30]) {
for (let x = 17; x <= 31; x++) {
onFace(x, z, (x + z) % 4 === 0 ? ropeLight : rope, burlap);
}
}
for (let x = 19; x <= 29; x++) {
onFace(x, 23 + Math.round((x - 19) * 0.55), ropeLight, burlap);
onFace(x, 29 - Math.round((x - 19) * 0.55), rope, burlap);
}
for (let z = 21; z <= 23; z++) {
for (let x = 23; x <= 25; x++) onFace(x, z, z === 22 ? ropeLight : "#80653e", burlap);
}
for (let z = 19; z <= 21; z++) onFace(25, z, ropeLight, burlap);
// A small stitched, red heraldic target on the chest.
for (let z = 24; z <= 28; z++) {
for (let x = 22; x <= 26; x++) {
const d = Math.abs(x - 24) + Math.abs(z - 26);
if (d <= 3) onFace(x, z, d >= 2 ? "#c7a55c" : "#8e3535", burlap);
}
}
onFace(24, 26, "#d3b86f", burlap);
// Neck cinch and the separate, lumpy sack head.
box(21, 21, 32, 27, 27, 33, rope);
box(22, 20, 33, 26, 21, 33, ropeLight);
for (let z = 33; z <= 41; z++) {
for (let y = 18; y <= 28; y++) {
for (let x = 18; x <= 30; x++) {
const dx = (x - 24) / 5.5;
const dy = (y - 23) / 4.7;
const dz = (z - 37) / 5;
if (dx * dx + dy * dy + dz * dz <= 1 + (rand() - 0.5) * 0.045) {
let shade = y <= 21 ? 2 : 1;
if (z >= 39) shade++;
if (rand() < 0.13) shade += rand() < 0.5 ? -1 : 1;
set(x, y, z, sack[Math.max(0, Math.min(3, shade))]);
}
}
}
}
// Visible seams and a few loose threads.
for (let z = 34; z <= 40; z++) {
onFace(19 + (z % 3 === 0 ? 1 : 0), z, "#776345", sack);
onFace(29 - (z % 3 === 0 ? 1 : 0), z, "#776345", sack);
}
for (const x of [21, 24, 27]) {
set(x, 24, 42, "#c9a95c");
set(x, 24, 43, "#ddbd70");
}
// Painted charcoal X eyes, following the rounded front surface.
for (const cx of [21, 27]) {
for (let d = -1; d <= 1; d++) {
onFace(cx + d, 38 + d, "#342d29", sack);
onFace(cx + d, 38 - d, "#342d29", sack);
}
}
onFace(24, 36, "#695444", sack);
for (const x of [22, 23, 24, 25, 26]) {
onFace(x, x === 24 ? 34 : 35, "#514039", sack);
}
// Dented old iron cooking pot worn as a helmet.
for (let z = 40; z <= 44; z++) {
for (let y = 18; y <= 28; y++) {
for (let x = 18; x <= 30; x++) {
const r = ((x - 24) / 6) ** 2 + ((y - 23) / 5.2) ** 2;
const limit = z === 40 ? 1 : z === 41 ? 0.96 : z === 42 ? 0.77 : z === 43 ? 0.49 : 0.17;
const dent = x >= 26 && y <= 23 && z >= 43;
if (r <= limit && !dent) {
const c = y <= 19 ? "#777975" : z >= 43 ? "#96978e" : "#646762";
set(x, y, z, rand() < 0.1 ? "#555853" : c);
}
}
}
}
for (let x = 18; x <= 30; x++) {
for (let y = 18; y <= 28; y++) {
if (((x - 24) / 6.4) ** 2 + ((y - 23) / 5.6) ** 2 <= 1) {
set(x, y, 40, y <= 19 ? "#444746" : "#555855");
}
}
}
for (const x of [20, 24, 28]) set(x, 17, 40, "#b4ae97");
set(26, 19, 43, "#555853");
set(27, 20, 42, "#414442");
// The pot's bent handle peeks out on one side.
line(30, 22, 41, 32, 22, 42, 0, "#494b49");
line(32, 22, 42, 32, 26, 42, 0, "#696b65");
line(32, 26, 42, 30, 27, 41, 0, "#494b49");
animate("upper dummy", { type: "spin", axis: "z", speed: 1.5 });import { draw } from "./assets/voxel";
import { munecoDeEntrenamiento } from "./assets/muneco-de-entrenamiento";
// En cada cuadro: parado sobre (x, y), el centro de su base.
draw(ctx, munecoDeEntrenamiento, x, y, { size: 64, angle: 0, t: performance.now() / 1000 });
// size: lo que mide en px su lado más largo
// angle: giro sobre el piso, en radianes
// t: segundos; si se mueve, se anima solo con eso# En una terminal, en la carpeta del juego
fogon assets add muneco-de-entrenamiento
# Con las herramientas de Fogón (conector)
add_assets({ slug: "<tu-juego>", ids: ["muneco-de-entrenamiento"] })
# Queda en client/assets/muneco-de-entrenamiento.ts
# https://fogon.page/object/muneco-de-entrenamiento