← Monsters and enemies3D assetfogon/murcielago
Murciélago
Drag to turn it// «Murciélago» · gpt-6-sol (medium)
// A chunky cartoon bat flying, centered: a round dark purple furry body with a cute-scary face toward -y, big pointy ears, glowing yellow eyes and two little white fangs, and two wide leathery wings spread out to the sides with visible finger bones and scalloped edges.
// Chunky flying bat. All geometry is lowered so its lowest blocks reach z=0.
const floorOffset = 10;
const put = (x, y, z, color) => set(x, y, z - floorOffset, color);
const oval = (x, y, z, rx, ry, rz, color) =>
ellipsoid(x, y, z - floorOffset, rx, ry, rz, color);
const cube = (x0, y0, z0, x1, y1, z1, color) =>
box(x0, y0, z0 - floorOffset, x1, y1, z1 - floorOffset, color);
const ball = (x, y, z, r, color) =>
sphere(x, y, z - floorOffset, r, color);
const tube = (x0, y0, z0, x1, y1, z1, r, color) =>
line(x0, y0, z0 - floorOffset, x1, y1, z1 - floorOffset, r, color);
const joint = (name, x, y, z) =>
part(name, [x, y, z - floorOffset]);
joint("body", 24, 23, 25);
animate("body", { type: "bob", axis: "z", speed: 3, amplitude: 2 });
const plum = "#382044";
const purple = "#512b60";
const litPurple = "#684078";
const shadow = "#291834";
function furryEllipsoid(cx, cy, cz, rx, ry, rz) {
for (let x = Math.floor(cx - rx); x <= cx + rx; x++)
for (let y = Math.floor(cy - ry); y <= cy + ry; y++)
for (let z = Math.floor(cz - rz); z <= cz + rz; z++) {
const q = ((x - cx) / rx) ** 2 +
((y - cy) / ry) ** 2 +
((z - cz) / rz) ** 2;
if (q > 1) continue;
let color = z < cz - 3 ? plum : purple;
if (z > cz + 2 && y < cy + 2) color = litPurple;
if (q > 0.72 && rand() < 0.12)
color = z < cz ? shadow : litPurple;
put(x, y, z, color);
}
}
// Round body, oversized head, and small fur tufts.
furryEllipsoid(24, 25, 22, 8, 7, 10);
furryEllipsoid(24, 20, 29, 9, 8, 9);
for (const [x, z] of [[18, 18], [21, 15], [24, 14], [27, 15], [30, 18]])
tube(x, 18, z + 3, x, 17, z, 1, plum);
// These two feet now reach the floor at z=0.
oval(19, 21, 12, 2, 2, 2, plum);
oval(29, 21, 12, 2, 2, 2, plum);
// Tall pointed ears with lighter inner patches.
for (const side of [-1, 1]) {
const cx = 24 + side * 8;
for (let z = 35; z <= 44; z++) {
const width = Math.max(0, Math.floor((44 - z) * 0.38));
const back = 23 - Math.floor((z - 35) * 0.12);
for (let x = cx - width; x <= cx + width; x++)
for (let y = 16; y <= back; y++)
put(x, y, z, z < 39 ? purple : plum);
if (z >= 37 && z <= 42 && width >= 1)
for (let x = cx - width + 1; x <= cx + width - 1; x++)
put(x, 15, z, z > 40 ? "#a66090" : "#864772");
}
}
// Muzzle, grin, fangs, and glowing yellow eyes.
oval(24, 13, 24, 5, 3, 3, "#714778");
oval(24, 10, 25, 2, 1, 1, "#9a668e");
cube(21, 9, 22, 27, 9, 22, shadow);
for (const x of [21, 27]) {
cube(x, 8, 21, x + 1, 8, 23, "#fff4e5");
put(x, 8, 20, "#e1d9e4");
}
for (const x of [19, 29]) {
oval(x, 12, 31, 3, 2, 3, shadow);
oval(x, 10, 31, 2, 1, 2, "#ffca36");
put(x, 9, 32, "#fff5a2");
put(x + 1, 9, 31, "#ffe875");
tube(x - 2, 10, 35, x + 2, 10, 34, 1, plum);
}
// Wing outline: the lower edge dips between its finger tips.
const outline = [
[8, 29], [11, 36], [16, 38], [22, 36],
[21, 31], [18, 33], [16, 27], [14, 30],
[12, 24], [10, 26], [8, 23]
];
function insideWing(d, z) {
let inside = false;
for (let i = 0, j = outline.length - 1; i < outline.length; j = i++) {
const a = outline[i], b = outline[j];
if ((a[1] > z) !== (b[1] > z) &&
d < (b[0] - a[0]) * (z - a[1]) / (b[1] - a[1]) + a[0])
inside = !inside;
}
return inside;
}
for (const side of [-1, 1]) {
const name = side < 0 ? "leftWing" : "rightWing";
joint(name, 24 + side * 8, 24, 29);
animate(name, {
type: "swing",
axis: "y",
speed: side < 0 ? 8 : -8,
amplitude: 0.5
});
for (let d = 8; d <= 22; d++)
for (let z = 23; z <= 38; z++) {
if (!insideWing(d + 0.5, z + 0.5)) continue;
const edge = !insideWing(d - 0.5, z + 0.5) ||
!insideWing(d + 1.5, z + 0.5) ||
!insideWing(d + 0.5, z - 0.5) ||
!insideWing(d + 0.5, z + 1.5);
const color = edge ? "#321b42" :
z >= 33 ? "#89518e" : (rand() < 0.17 ? "#754079" : "#63336c");
for (let y = 24; y <= 25; y++)
put(24 + side * d, y, z, color);
}
// Raised finger bones fan outward from each shoulder.
const bone = "#b078ab";
tube(24 + side * 8, 23, 29, 24 + side * 11, 23, 35, 1, "#a46b9c");
tube(24 + side * 11, 23, 35, 24 + side * 16, 23, 37, 0, bone);
tube(24 + side * 11, 23, 35, 24 + side * 22, 23, 35, 0, bone);
tube(24 + side * 11, 23, 35, 24 + side * 18, 23, 32, 0, bone);
tube(24 + side * 10, 23, 31, 24 + side * 16, 23, 27, 0, bone);
tube(24 + side * 9, 23, 28, 24 + side * 12, 23, 24, 0, bone);
ball(24 + side * 11, 23, 35, 1, "#bd83ae");
}import { draw } from "./assets/voxel";
import { murcielago } from "./assets/murcielago";
// Every frame: standing on (x, y), the centre of its base.
draw(ctx, murcielago, 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 murcielago
# With Fogón's tools (connector)
add_assets({ slug: "<your-game>", ids: ["murcielago"] })
# It lands in client/assets/murcielago.ts
# https://fogon.page/object/murcielago