← Piezas de nivel 2Objeto 3Dfogon/nivel-bolsa-de-monedas
Bolsa de monedas
Arrastrá para girarlo// «Bolsa de monedas» · gpt-6-sol (medium)
// A fat brown burlap money sack tied at the top with a golden rope, a big gold coin symbol painted on the front (-y), with a few gold coins spilling out of the opening and three coins lying on the floor in front of it.
// A plump burlap sack, centered behind three spilled coins.
const cx = 24, cy = 27;
const cloth = ["#9b5c2e", "#ad6b34", "#bb7940", "#c8894b", "#d49656"];
const gold = "#ffcb35", goldLight = "#ffe375", goldDark = "#d98a18";
// Shape the sack layer by layer: broad belly, gathered shoulders, narrow neck.
const profile = [
[0, 8, 7], [3, 12, 11], [8, 16, 14], [15, 17, 16],
[21, 17, 16], [26, 15, 14], [30, 11, 10],
[32, 8, 8], [35, 7, 7]
];
function radii(z) {
for (let i = 1; i < profile.length; i++) {
if (z <= profile[i][0]) {
const a = profile[i - 1], b = profile[i];
const t = (z - a[0]) / (b[0] - a[0]);
return [a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t];
}
}
return [7, 7];
}
function frontY(x, z) {
for (let y = 8; y <= 43; y++) if (get(x, y, z)) return y;
return null;
}
for (let z = 0; z <= 35; z++) {
const [rx, ry] = radii(z);
for (let x = 6; x <= 42; x++) {
for (let y = 10; y <= 43; y++) {
const d = ((x - cx) / rx) ** 2 + ((y - cy) / ry) ** 2;
if (d > 1) continue;
let shade = z < 7 ? 0 : z < 16 ? 1 : z < 27 ? 2 : 3;
if (y < cy - 5) shade++;
if (x > cx + 10) shade--;
if (rand() < 0.035) shade += rand() < 0.5 ? -1 : 1;
set(x, y, z, cloth[Math.max(0, Math.min(4, shade))]);
}
}
}
// A few subtle folds and individual stitches on the visible burlap.
for (const x of [12, 16, 33, 37]) {
for (let z = 7; z <= 25; z++) {
const y = frontY(x, z);
if (y !== null && (z + x) % 5 !== 0)
set(x, y - 1, z, z % 4 === 0 ? "#d99a59" : "#9e602f");
}
}
for (let x = 12; x <= 36; x += 3) {
const y = frontY(x, 6);
if (y !== null) set(x, y - 1, 6, "#db9d5c");
}
// Large, curved gold coin emblem painted across the front.
for (let z = 8; z <= 28; z++) {
for (let x = 14; x <= 34; x++) {
const d = Math.sqrt(((x - 24) / 10.5) ** 2 + ((z - 18) / 10.5) ** 2);
if (d > 1) continue;
const y = frontY(x, z);
if (y === null) continue;
const rim = d > 0.82;
set(x, y - 1, z, rim ? goldDark : (z > 19 ? goldLight : gold));
if (rim && z > 19 && x < 29) set(x, y - 2, z, "#ffdf69");
}
}
// An embossed dollar mark, including its two short vertical strokes.
const mark = [
"...###...",
"..#...#..",
"..#......",
"...###...",
"......#..",
"......#..",
"..#...#..",
"...###..."
];
for (let row = 0; row < mark.length; row++) {
const z = 23 - row;
for (let col = 0; col < 9; col++) {
if (mark[row][col] !== "#") continue;
const x = 20 + col, y = frontY(x, z);
if (y !== null) set(x, y - 2, z, "#ae6912");
}
}
for (let z = 13; z <= 25; z++) {
const y = frontY(24, z);
if (y !== null) set(24, y - 2, z, "#ae6912");
}
// Gathered, open top: dark hollow center and a thick folded burlap lip.
for (let z = 36; z <= 38; z++) {
for (let x = 15; x <= 33; x++) {
for (let y = 18; y <= 36; y++) {
const d = ((x - 24) / 9) ** 2 + ((y - 27) / 8) ** 2;
if (d <= 1 && d >= 0.49)
set(x, y, z, z === 38 ? "#df9b58" : "#b5753b");
else if (d < 0.49 && z === 36)
set(x, y, z, "#704020");
}
}
}
for (let x = 16; x <= 32; x++) {
for (let y = 19; y <= 35; y++) {
const d = ((x - 24) / 8.5) ** 2 + ((y - 27) / 7.5) ** 2;
if (d > 0.70 && d <= 1) set(x, y, 39, "#e5a35e");
}
}
// Chunky golden rope wrapped around the neck.
for (let a = 0; a < 360; a += 3) {
const t = a * Math.PI / 180;
const x = Math.round(24 + 8.1 * Math.cos(t));
const y = Math.round(27 + 8.0 * Math.sin(t));
set(x, y, 32, goldDark);
set(x, y, 33, a % 18 < 9 ? goldLight : gold);
}
sphere(24, 18, 33, 2, gold);
set(23, 16, 34, "#fff0a0");
line(22, 17, 32, 19, 15, 27, 1, goldDark);
line(26, 17, 32, 29, 15, 28, 1, gold);
set(19, 15, 27, goldLight);
set(29, 15, 28, goldLight);
// Coins visible at the mouth, with a couple leaning out toward the viewer.
function uprightCoin(x, y, z, r) {
for (let dx = -r; dx <= r; dx++) {
for (let dz = -r; dz <= r; dz++) {
const d = dx * dx + dz * dz;
if (d > r * r) continue;
for (let yy = y; yy <= y + 2; yy++)
set(x + dx, yy, z + dz,
d > (r - 0.8) ** 2 ? goldDark : (dz > 0 ? goldLight : gold));
}
}
set(x, y - 1, z, "#fff0a0");
}
uprightCoin(20, 23, 38, 3);
uprightCoin(27, 25, 39, 3);
uprightCoin(29, 19, 36, 2);
uprightCoin(18, 20, 36, 2);
// Three separate, thick coins lying flat on the floor in front.
function floorCoin(x, y, r) {
for (let dx = -r; dx <= r; dx++) {
for (let dy = -r; dy <= r; dy++) {
const d = dx * dx + dy * dy;
if (d > r * r) continue;
set(x + dx, y + dy, 0, goldDark);
set(x + dx, y + dy, 1,
d > (r - 0.9) ** 2 ? "#e99c1f" : gold);
if (d <= (r - 0.9) ** 2)
set(x + dx, y + dy, 2, dy < 0 ? goldLight : gold);
}
}
set(x, y, 3, "#fff0a0");
set(x + 1, y, 3, goldDark);
}
floorCoin(14, 5, 3);
floorCoin(24, 6, 4);
floorCoin(35, 5, 3);import { draw } from "./assets/voxel";
import { nivelBolsaDeMonedas } from "./assets/nivel-bolsa-de-monedas";
// En cada cuadro: parado sobre (x, y), el centro de su base.
draw(ctx, nivelBolsaDeMonedas, 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 nivel-bolsa-de-monedas
# Con las herramientas de Fogón (conector)
add_assets({ slug: "<tu-juego>", ids: ["nivel-bolsa-de-monedas"] })
# Queda en client/assets/nivel-bolsa-de-monedas.ts
# https://fogon.page/object/nivel-bolsa-de-monedas