← Basics3D assetfogon/puentecito
Puentecito
Drag to turn it// «Puentecito» · gpt-6-sol (medium)
// Footbridge («Puentecito»).
const C = {
dirt: ["#60432f", "#704e35", "#80593b"],
grass: ["#648d43", "#76a04b", "#85ad53", "#94b75e"],
water: ["#2499a8", "#2baabb", "#39b8c6"],
woodDark: "#77502e",
woodSide: "#996638",
wood: ["#af7941", "#bd884b", "#ca9554"],
woodTop: ["#d5a362", "#e3b371", "#e9be7d"],
woodLight: "#efc484"
};
// Low, slightly irregular square of earth.
for (let x = 4; x <= 43; x++) {
for (let y = 4; y <= 43; y++) {
if ((x < 7 || x > 40) && (y < 7 || y > 40)) continue;
for (let z = 0; z <= 3; z++)
set(x, y, z, C.dirt[Math.min(2, Math.floor(rand() * 3))]);
set(x, y, 4, C.grass[Math.floor(rand() * C.grass.length)]);
}
}
// A stream crosses beneath the bridge. Its uneven grassy banks remain visible.
for (let x = 5; x <= 42; x++) {
const near = 14 + Math.round(1.3 * Math.sin(x * 0.48));
const far = 32 + Math.round(1.2 * Math.sin(x * 0.39 + 1));
for (let y = near; y <= far; y++) {
set(x, y, 4, C.water[Math.floor(rand() * C.water.length)]);
if (rand() < 0.12)
set(x, y, 4, "#46c3ce");
}
for (const y of [near - 1, far + 1]) {
if (y >= 5 && y <= 42)
set(x, y, 4, C.grass[Math.floor(rand() * C.grass.length)]);
}
}
// Bright, broken reflections between the bridge and the shore.
for (const [x, y, len] of [
[34, 19, 3], [38, 23, 2], [35, 27, 4],
[10, 23, 2], [31, 30, 2], [40, 17, 2]
]) {
for (let i = 0; i < len; i++) set(x + i, y, 5, "#a1e6da");
}
// Tufts of grass along the water's edge.
for (let i = 0; i < 105; i++) {
const x = 6 + Math.floor(rand() * 36);
const y = rand() < 0.5
? 9 + Math.floor(rand() * 6)
: 33 + Math.floor(rand() * 7);
if (get(x, y, 4) && get(x, y, 4)[1] !== "2") {
set(x, y, 5, rand() < 0.5 ? "#82ac50" : "#99ba60");
if (rand() < 0.18) set(x, y, 6, "#9abc62");
}
}
// A few pale river stones.
for (const [x, y] of [[36, 34], [39, 35], [9, 34], [11, 32], [38, 12]]) {
box(x, y, 5, x + 1, y + 1, 5, "#838d79");
set(x, y, 6, "#b5b7a0");
set(x + 1, y, 6, "#a4aa91");
}
// The bridge runs from the near bank to the far bank.
function deckHeight(y) {
if (y <= 19) return 6 + Math.floor((y - 6) * 7 / 13);
if (y <= 27) return 13;
return 13 - Math.floor((y - 27) * 7 / 14);
}
// Heavy beams following the rise and fall of the deck.
for (let y = 6; y <= 41; y++) {
const h = deckHeight(y);
for (const x of [15, 16, 29, 30]) {
box(x, y, h - 3, x, y, h - 1,
x === 15 || x === 30 ? C.woodDark : C.woodSide);
}
// Solid crosswise planks, individually readable as little steps.
for (let x = 16; x <= 29; x++) {
set(x, y, h - 1, C.wood[Math.floor(rand() * C.wood.length)]);
set(x, y, h,
rand() < 0.16 ? C.woodTop[0] : C.woodTop[(y + x) % 3]);
}
set(16, y, h, "#f0c17e");
set(29, y, h, "#b37d42");
// Dark seams emphasize the separate boards.
if (y % 3 === 0)
for (let x = 18; x <= 27; x++)
set(x, y, h, "#b48148");
}
// Stout feet planted on either bank, leaving the channel open below.
for (const y of [9, 12, 36, 39]) {
for (const x of [15, 30])
box(x, y, 5, x + 1, y + 1, deckHeight(y) - 2, C.woodDark);
}
// Rail posts on both sides, with square pale caps.
const posts = [7, 12, 17, 22, 27, 32, 37, 41];
for (const x of [14, 31]) {
for (const y of posts) {
const h = deckHeight(y);
box(x, y, h - 2, x + 1, y + 1, h + 8, C.woodSide);
box(x, y, h + 8, x + 1, y + 1, h + 9, C.woodTop[1]);
box(x, y, h + 10, x + 1, y + 1, h + 10, C.woodLight);
set(x + 1, y + 1, h + 8, C.woodDark);
}
// Continuous handrails and a lower rail follow the arched walkway.
for (let y = 7; y < 41; y++) {
const h = deckHeight(y);
box(x, y, h + 7, x + 1, y + 1, h + 8, C.woodTop[0]);
set(x, y, h + 9, C.woodLight);
box(x, y, h + 3, x, y + 1, h + 4, C.woodSide);
set(x, y, h + 4, "#cb9655");
}
// Slim upright balusters between the main posts.
for (let y = 9; y <= 39; y += 2) {
if (posts.some(p => Math.abs(p - y) <= 1)) continue;
const h = deckHeight(y);
box(x, y, h + 1, x, y, h + 7, "#a8733c");
set(x, y, h + 5, "#d09c5d");
}
}
// Chunky end steps, complete with light-colored nosings.
for (const y of [6, 7, 8, 9, 10, 11, 38, 39, 40, 41]) {
const h = deckHeight(y);
for (let x = 17; x <= 28; x++)
if (x % 4 !== 0) set(x, y, h, "#dfad6c");
box(17, y, h, 28, y, h, "#d8a362");
for (const x of [17, 21, 25, 28]) set(x, y, h, "#ebba78");
}
// Small sprigs on the grassy bank.
for (const [x, y] of [[7, 12], [11, 38], [35, 37], [40, 35], [39, 11]]) {
line(x, y, 5, x, y, 7, 0, "#547b3c");
set(x - 1, y, 6, "#82a74a");
set(x + 1, y, 6, "#8daf54");
}import { draw } from "./assets/voxel";
import { puentecito } from "./assets/puentecito";
// Every frame: standing on (x, y), the centre of its base.
draw(ctx, puentecito, x, y, { size: 64, angle: 0, t: performance.now() / 1000 });
// size: its longest side, in px
// angle: turn on the floor, in radians
// t: seconds; if it moves, that animates it# In a terminal, in the game's folder
fogon assets add puentecito
# With Fogón's tools (connector)
add_assets({ slug: "<your-game>", ids: ["puentecito"] })
# It lands in client/assets/puentecito.ts
# https://fogon.page/object/puentecito