Volver a Huggybugi
Español

Cómo crear personajes y objetos

Un PNG por acción, con los cuadros pegados en fila horizontal.

Usar la skill · lo más fácil

Sube el zip de la skill a Claude, ChatGPT o Gemini junto con una foto. Te muestra el dibujo, lo corrige contigo y te devuelve un zip listo para usar.

Cómo conseguir y usar la skill

Copiar la especificación y pedirlo

Copia esta página en markdown, pégala en ChatGPT o Claude y seguirá la especificación.

Subir

En el menú de la bandeja entra a "Personaje > Crear…", elige la pestaña Personajes, Objetos o Emotes y pulsa "+ Crear". Ponle un nombre (hasta 20 caracteres) y elige un PNG para cada acción, o arrástralo a la casilla. Los campos de fps vienen con los valores recomendados. En los objetos también eliges el tipo (para lanzar, comida, caca) y un wav de sonido. Al guardar aparece en tu lista, y ahí mismo lo editas o lo borras. La tarjeta + al inicio de las ventanas de personajes, objetos y emotes abre el mismo editor.

Les llega a tus amigos cuando abren la ventana "Personaje > Cambiar…" de la bandeja o pulsan "Traer personajes y objetos nuevos". El personaje que elegiste se ve de inmediato.

Para la comida, haz clic derecho en un amigo, elige una en "Comida" y luego haz clic en él para lanzarla. Cada clic le da de comer hasta que vuelvas a elegir algo para lanzar.

En la caca, la imagen fly se dibuja como caca puesta en el suelo (no se lanza ni se come). Tu personaje deja la caca que elegiste cuando está lleno. Los amigos que todavía no la han recibido ven la caca por defecto.

Emotes

En la pestaña "Emotes" elige un GIF, WebP, APNG, PNG o JPG. La app lo reduce a 64px, toma hasta 16 cuadros y lo guarda a su velocidad original (el original, hasta 20MB y 2048px por lado). El nombre admite hasta 20 caracteres y no puede repetirse dentro del grupo. : y los espacios no se permiten porque cortarían :nombre: dentro del mensaje, y los nombres de los emotes básicos (heart·question·exclamation·angry·lol·note) ya están tomados.

Dónde se usan: 'Emotes' en el menú que sale al hacer clic en tu personaje (⌃⌥⌘E, Ctrl+Alt+E en Windows) abre la ventana sobre tu cabeza. Escribe : en un mensaje para ver los nombres, y al elegir uno entra como :nombre: en el globo. Con el botón de emoji de la pestaña 'Estado' lo pones en tu estado. También puedes reaccionar con uno a un mensaje del chat. Solo lo ven los amigos del mismo grupo.

Especificaciones

Acciones del personaje

Basta con idle, walk y sleep. Las demás, si faltan, se reemplazan por otra acción. Sin offline, al desconectarse se convierte en piedra.

AcciónCuándoRecomendadoSi falta
idle obligatorioQuieto2 cuadros, 2fps
walk obligatorioAl caminar4 cuadros, 6fps
sleep obligatorio30 minutos ausente o pantalla bloqueada (la app dibuja el Zzz)2 cuadros, 1fps
pokedAl picarlo2 cuadros, 8fpsidle
throwAl lanzar3 cuadros, 10fpsidle
hitAl recibir el golpe2 cuadros, 8fpspoked
jumpAl esquivar, acción de salto2 cuadros, 8fpsidle
eatAl recibir comida, acción de ñam3 cuadros, 6fpshappy
waveSaludo a un amigo que se conecta, acción de saludo3 cuadros, 6fpshappy
happyAl recibir caricias, acción de alegría2 cuadros, 4fpsidle
offlineDesconectado (caído)1 cuadro, 1fpsPiedra

Acciones del objeto

AcciónCuándoRecomendadoSi falta
fly obligatorioMientras vuela. Activa 'Girar al volar' en la ventana de creación y la app lo gira por ti (en lo nuevo viene apagado). Con el giro apagado, lo que se lanza vuela derecho mirando hacia donde va, así que dibújalo mirando a la derecha; la comida con el giro apagado vuela tal como la dibujaste, así que dibújala derecha. Dale movimiento con varios cuadros para que no se vea congelada (que rebote, que brille). La caca se dibuja puesta en el suelo1 cuadro para lanzar, de 1 a 4 para comida, 12fps (la caca, 1 cuadro)
impactEl momento del impacto (solo para lanzar; la comida se come al llegar)3 cuadros, 12fpsEl estallido de estrellas que dibuja la app

El wav suena en el momento del impacto (en la caca, al caer). Sin él, lo que se lanza y la comida no suenan, y la caca usa el 'pum' de la app.

Ejemplo: martillo de juguete

Es un objeto básico. fly es un solo cuadro, pero gira mientras vuela (la comida no gira).

AcciónMovimientoLo que subes (4x)
fly
Mientras vuela
Martillo de juguete flyfly 1 cuadro
impact
El momento del impacto
Martillo de juguete impactimpact 3 cuadros

Descargar a tamaño real: fly, impact, Descargar todo (zip)

Caca

Un objeto de caca solo necesita un cuadro fly. Se dibuja puesto en el suelo, sin girar. La app no lo reduce: dibuja la cuadrícula de 48×48 tal cual, así que hazlo chiquito abajo al centro (la caca por defecto mide 17 de ancho y 12 de alto) y deja la parte de arriba vacía. No tiene que parecer caca. También sirve cualquier objetito que le quede a tu personaje (una calabacita, un ataúd, un ovillo de lana) siempre que entre en el área de 24×24 de abajo al centro. Abajo está la caca por defecto.

AcciónMovimientoLo que subes (4x)
fly
Puesta en el suelo
Caca por defecto flyfly 1 cuadro

Descargar a tamaño real: fly

Ejemplo: Ddeoggi

Todas las acciones de Ddeoggi, dibujado a partir de un pingüino de peluche. Usa la misma cantidad de cuadros de la tabla de arriba y, como todas las poses reutilizan el mismo cuerpo, las acciones no se desalinean entre sí.

AcciónMovimientoLo que subes (4x)
idle
Quieto
Ddeoggi idleidle 2 cuadros
walk
Al caminar
Ddeoggi walkwalk 4 cuadros
sleep
Dormido
Ddeoggi sleepsleep 2 cuadros
poked
Al picarlo
Ddeoggi pokedpoked 2 cuadros
throw
Al lanzar
Ddeoggi throwthrow 3 cuadros
hit
Al recibir el golpe
Ddeoggi hithit 2 cuadros
jump
Al esquivar y saltar
Ddeoggi jumpjump 2 cuadros
eat
Al recibir comida
Ddeoggi eateat 3 cuadros
wave
Saludo
Ddeoggi wavewave 3 cuadros
happy
Caricias
Ddeoggi happyhappy 2 cuadros
offline
Desconectado
Ddeoggi offlineoffline 1 cuadro

Descargar a tamaño real: idle, walk, sleep, poked, throw, hit, jump, eat, wave, happy, offline, Descargar todo (zip)

Plantillas

Con "Bajar plantilla" en la ventana de creación obtienes una plantilla con el hámster de la app dibujado en cada acción (una bellota para los objetos). Las plantillas vacías están aquí: 2 cuadros, 4 cuadros, 8 cuadros, Descargar todo (zip)

Ponla en una capa de abajo, dibuja encima y apaga la capa de la plantilla antes de guardar. La línea roja marca dónde van los pies.

Programas para dibujar: Piskel(web, gratis), LibreSprite(gratis), Aseprite(de pago)

Crear con IA

Las IA de imagen no aciertan exactamente los 48 píxeles. Genera en grande, reduce y después retoca.

  1. Generar con el prompt de abajo
  2. Reducir cada cuadro a 48×48. La interpolación tiene que ser vecino más cercano (Nearest Neighbor), si no queda borroso.
  3. Dejar el fondo transparente y alinear los cuadros

En el navegador, sin instalar nada:

El aspecto base

Pixel art sprite of a cute [animal], side view facing right, full body,
match the body shape, proportions and features of the attached picture if any
(otherwise the real proportions of that animal or character),
on a 48x48 pixel grid, upscaled so one art pixel is a clean 16x16 block
(768x768 per frame), hard edges, no anti-aliasing, no gradients, no dithering,
about 8 colors, 1px dark outline, flat shading, transparent background,
fills most of the grid (about 30-46 pixels tall), feet on the bottom row,
horizontally centered, nothing touching the top or side edges, no shadow, no text

Hoja de acciones (adjunta el resultado anterior como referencia)

Pixel art sprite sheet of the same character, [N] frames in one horizontal row,
no gaps, facing right, animation: [motion],
each frame on a 48x48 pixel grid, upscaled so one art pixel is a clean 16x16 block
(768x768 per frame), hard edges, no anti-aliasing, no gradients, no dithering,
same palette (about 8 colors) as the reference, 1px dark outline,
same size and position in every frame, feet on the bottom row,
transparent background, the 4 corner pixels of every frame transparent,
nothing touching the top or side edges, no shadow, no text
Acción[N][motion]Ddeoggi
idle2gentle breathing, body down 1px in frame 2Ddeoggi idle
walk4walk cycle: left foot forward, body up, right foot forward, body upDdeoggi walk
sleep2eyes closed, body rises and falls 1pxDdeoggi sleep
poked2squashed flat, then back to normalDdeoggi poked
throw3wind up, then swing one arm forwardDdeoggi throw
hit2knocked back, dizzy swirl eyesDdeoggi hit
jump2crouch, then stretch upDdeoggi jump
eat3mouth open, chewing, cheeks puffedDdeoggi eat
wave3one paw raised, wavingDdeoggi wave
happy2smiling closed eyes, tiny heartDdeoggi happy
offline1knocked out, squashed flat on the ground, X eyesDdeoggi offline

Objetos

Pixel art game item, [item], one single frame on a 48x48 pixel grid,
upscaled so one art pixel is a clean 16x16 block (768x768), hard edges,
no anti-aliasing, no gradients, no dithering, centered in the frame,
about 8 colors, 1px dark outline, transparent background,
the 4 corner pixels transparent, no shadow, no text

Para algo que se lanza, dibuja un solo cuadro fly como el martillo y deja que la app lo gire con 'Girar al volar'. Si tiene dirección, como una flecha o una garra, apágalo y dibújalo mirando a la derecha: Martillo de juguete fly Martillo de juguete impact

Lo que se lanza necesita su propio impact (la comida y la caca no lo tienen):

Pixel art impact effect sprite sheet, 3 frames in one horizontal row, no gaps,
a star bursting from the center of the frame and breaking into pieces,
each frame on a 48x48 pixel grid, upscaled so one art pixel is a clean 16x16 block
(768x768 per frame), hard edges, no anti-aliasing, no gradients, no dithering,
centered in every frame, about 8 colors, 1px dark outline, transparent background,
the 4 corner pixels of every frame transparent, no shadow, no text

Con 'Girar al volar' apagado, la comida vuela derecha. Usa varios cuadros para darle movimiento:

Pixel art food item, [item], [N] frames in one horizontal row, no gaps,
upright in every frame (not spinning), gently bobbing up and down with a small sparkle,
each frame on a 48x48 pixel grid, upscaled so one art pixel is a clean 16x16 block
(768x768 per frame), hard edges, no anti-aliasing, no gradients, no dithering,
centered in every frame, about 8 colors, 1px dark outline, transparent background,
the 4 corner pixels of every frame transparent, no shadow, no text

Caca

[character] es el nombre y el aspecto de tu personaje (por ejemplo "Ddeoggi, un pingüino gris de pico amarillo").

Pixel art of a cute squat swirl poop with a tiny face colored and decorated like
[character] (one feature such as ears or beak), or a small prop that matches
[character] (for example a mini pumpkin or a coffin), one single frame on a 48x48 pixel grid,
upscaled so one art pixel is a clean 16x16 block (768x768), hard edges,
no anti-aliasing, no gradients, no dithering, small (about 17x12 pixels),
horizontally centered, sitting on the bottom row, entirely inside the bottom-center
24x24 area of the grid (x 12-35, y 24-47), about 8 colors, 1px dark outline,
transparent background, no shadow, no text

Usar la skill (lo más fácil)

Descarga huggybugi-character.zip, súbelo a Claude, ChatGPT o Gemini junto con una foto y dile "sigue SKILL.md y crea un personaje de Huggybugi con esta foto". Dibuja las 11 acciones (y también caca, algo para lanzar o comida si se lo pides) y te devuelve un zip listo para 'Subir todo de una vez', aquí abajo. La IA tiene que poder ejecutar código (en Claude y ChatGPT, usa un modelo que lo haga). Si te cansa subirlo cada vez, regístralo como skill. Después basta con darle una foto y decir "créame un personaje de Huggybugi".

Claude Code y Codex lo instalan como plugin, sin zip, y al actualizar el plugin llegan las versiones nuevas. Estos comandos y la especificación también se copian desde "Crear con IA" en la ventana de creación de la app.

# Claude Code
/plugin marketplace add Zaehyeon2/huggybugi
/plugin install huggybugi@huggybugi

# Codex
codex plugin marketplace add Zaehyeon2/huggybugi
codex plugin add huggybugi@huggybugi

Los nombres de los menús cambian de vez en cuando. Los detalles están en el README.md dentro del zip.

IA que ejecuta código (ChatGPT, Claude y similares)

La cuadrícula sale exacta. Define el cuerpo, los ojos y los pies con figuras simples como elipses, y arma todas las acciones con una sola función que reciba los valores de la pose (alto del cuerpo, forma del ojo, pie levantado) y dibuje un cuadro. Así las acciones nunca se desalinean. Si le das una foto o un dibujo, copia los colores y el aspecto. Sin foto, pídele que primero busque algunos pixel art bonitos de ese animal u objeto y queda mucho mejor.

Write and run a Python (Pillow) script that draws a pixel art [animal] (see the attached
picture) for a 48x48 desktop pet. Match the picture's body shape, proportions and
features as closely as the grid allows (no picture: first look up a few cute pixel art references of that animal or item and follow their proportions, without copying any one of them). Build each frame from simple shapes (ellipses for body,
face, feet) in ONE function draw(pose) that takes pose values like bob, eyes, foot lift,
so every animation reuses the same shapes. Add a 1px dark outline automatically around
the filled pixels. Facing right, feet touching row 47, the 4 corner pixels transparent.
Save one horizontal strip PNG per animation (1x, frames 48x48, no gaps), each file named
exactly after the animation (idle.png, walk.png, ...):
idle 2, walk 4, sleep 2, poked 2, throw 3, hit 2, jump 2, eat 3, wave 3, happy 2, offline 1.
Put all the PNGs in one zip file for download.
Show me an 8x preview, then let me ask for changes.

Los objetos y la caca tienen otras acciones, así que usa esto en lugar del prompt de arriba. Deja solo la línea de tu tipo, para lanzar o comida, y borra la otra.

Write and run a Python (Pillow) script that draws a pixel art [item] (see the attached
picture) for a 48x48 desktop pet app. Build it from simple shapes with a 1px dark outline,
transparent background, centered in the frame, the 4 corner pixels of every frame
transparent. Save horizontal strip PNGs (1x, frames 48x48, no gaps), keeping only the
line for your item kind:
thrown item: fly 1 facing right (with 'spin while flying' on, the app spins it, so do not draw spin frames)
and impact 3 (the moment it hits).
food: fly 1-4 drawn upright (it is not spun), gently bobbing with a small sparkle,
no impact (food is eaten on arrival). Name the files exactly fly.png and impact.png and
put them in one zip file. Show me an 8x preview, then let me ask for changes.
Write and run a Python (Pillow) script that draws a pixel art poop for a 48x48 desktop pet,
colored and decorated like [character] (one feature such as ears or beak), or a small prop
that matches [character] instead of a poop. One 48x48 frame
saved as fly.png: small (about 17x12 pixels), horizontally centered, sitting on the bottom
row, entirely inside the bottom-center 24x24 area (x 12-35, y 24-47), flat and cute,
1px dark outline, transparent background. Show me an 8x preview.

El código de un objeto se ve más o menos así (el fly y el impact del martillo, y la caca por defecto). Las imágenes del martillo y de la caca de los ejemplos de arriba salieron de este código. Los archivos se llaman igual que la acción, fly.png e impact.png; el ejemplo de abajo dibuja dos objetos a la vez, así que les pone un prefijo.

import math

COLORS = {'K': '#2b2233', 'Y': '#ffd23f', 'R': '#ff5a6e', 'F': '#9a6a48', 'N': '#7a4a24',
          'E': '#1b1420', 'P': '#ff9fb0'}
N = 48    # cuadros de 48x48

def oval(x, y, cx, cy, rx, ry):  # true si el centro del píxel (+0.5) queda dentro de la elipse
    return ((x + 0.5 - cx) / rx) ** 2 + ((y + 0.5 - cy) / ry) ** 2 <= 1

# arma un cuadro con una letra de color por píxel y convierte el borde exterior en una línea oscura de 1px (K).
def render(fill):
    g = [[fill(x, y) for x in range(N)] for y in range(N)]
    solid = lambda x, y: 0 <= x < N and 0 <= y < N and g[y][x] != '.'
    return [''.join('K' if c != '.' and not (solid(x - 1, y) and solid(x + 1, y)
                                             and solid(x, y - 1) and solid(x, y + 1)) else c
                    for x, c in enumerate(row)) for y, row in enumerate(g)]

# martillo de juguete: un cuadro fly (con 'girar al volar' activado, la app lo gira)
def hammer(x, y):
    if 12 <= x <= 35 and 10 <= y <= 21:
        return 'Y' if x <= 14 or x >= 33 else 'R'
    if 21 <= x <= 26 and 22 <= y <= 41:
        return 'Y'
    return '.'

# impact: una estrella crece y se rompe en pedazos (3 cuadros)
def star(r):
    def fill(x, y):
        dx, dy = x + 0.5 - 24, y + 0.5 - 24
        a = math.atan2(dy, dx) + math.pi / 2
        edge = r * (0.55 + 0.45 * abs(math.cos(a * 5 / 2)))
        return 'Y' if math.hypot(dx, dy) <= edge else '.'
    return fill

def burst(color, k, n=6):
    def fill(x, y):
        r, s = 10 + k * 5, 4.4 - k * 0.45
        for i in range(n):
            a = i / n * math.pi * 2 + k * 0.2
            if oval(x, y, 24 + math.cos(a) * r, 24 + math.sin(a) * r, s, s):
                return color
        return '.'
    return fill

# caca: un cuadro fly. tres rollos que tocan la fila de abajo, con ojos, cachetes y boca en el de más abajo
TIERS = [(23.5, 38.8, 3.4, 1.9), (23.5, 41.9, 5.8, 2.2), (23.5, 44.9, 8.1, 3.1)]
DOTS = {(22, 36): 'F', (23, 36): 'F', (24, 36): 'F', (20, 44): 'E', (26, 44): 'E',
        (18, 45): 'P', (19, 45): 'P', (27, 45): 'P', (28, 45): 'P',
        (22, 46): 'N', (23, 46): 'N', (24, 46): 'N'}

def poop(x, y):
    if (x, y) in DOTS:
        return DOTS[(x, y)]
    for i, (cx, cy, rx, ry) in enumerate(TIERS):
        if oval(x, y, cx, cy, rx, ry):
            # la parte baja de cada rollo es la ranura donde se apoya sobre el de abajo
            return 'N' if i < len(TIERS) - 1 and y >= cy + ry - 1.1 else 'F'
    return '.'

ANIMS = {
    'hammer-fly': [render(hammer)],
    'hammer-impact': [render(star(9)), render(star(15)), render(burst('Y', 2))],
    'poop-fly': [render(poop)],
}

# un PNG en tira horizontal por acción (1x, cuadros de 48x48)
def save(name, frames):
    from PIL import Image, ImageColor
    img = Image.new('RGBA', (N * len(frames), N))
    for i, g in enumerate(frames):
        for y, row in enumerate(g):
            for x, c in enumerate(row):
                if c != '.':
                    img.putpixel((i * N + x, y), ImageColor.getrgb(COLORS[c]))
    img.save(name + '.png')

if __name__ == '__main__':
    for name, frames in ANIMS.items():
        save(name, frames)

El código que escribe una IA se ve más o menos así (Ddeoggi, las 11 acciones). Las imágenes de Ddeoggi de los ejemplos de arriba salieron de este código. Si tienes Pillow, lo ejecutas y se guarda un PNG por acción.

import math

COLORS = {'K': '#3a3640', 'G': '#9a98a3', 'g': '#7f7d88', 'W': '#fbf9f4', 'Y': '#f7c535',
          'y': '#e2a51f', 'E': '#2a1c18', 'P': '#ffb3c0', 'R': '#e8586a'}
N = 48    # cuadros de 48x48
S = 1.5   # pasa a 48 las medidas definidas para 32

def rnd(v):  # redondea .5 hacia arriba (round de python va al par, así que no se usa)
    return math.floor(v + .5)

def u(v):  # los desplazamientos de la pose, en píxeles enteros de la cuadrícula de 48
    return int(math.copysign(rnd(abs(v) * S), v)) if v else 0

def oval(x, y, cx, cy, rx, ry):
    return ((x + .5 - cx) / rx) ** 2 + ((y + .5 - cy) / ry) ** 2 <= 1

def tri(x, y, a, b, c):
    px, py = x + .5, y + .5
    s = lambda p, q: (px - q[0]) * (p[1] - q[1]) - (p[0] - q[0]) * (py - q[1])
    d = (s(a, b), s(b, c), s(c, a))
    return not (min(d) < 0 and max(d) > 0)

# un ojo (base 3x3). dx, dy se miden desde la esquina superior izquierda del ojo
def eye(dx, dy, kind):
    if kind == 'open':
        return ('W' if dx == dy == 0 else 'E') if 0 <= dx <= 2 and 0 <= dy <= 2 else None
    if kind == 'wide':  # sorpresa: 4x4 con un brillo
        return ('W' if dx == dy == -1 else 'E') if -1 <= dx <= 2 and -1 <= dy <= 2 else None
    hit = {'closed': (dy == 1 and dx in (-1, 3)) or (dy == 2 and 0 <= dx <= 2),
           'happy': (dy == 0 and 0 <= dx <= 2) or (dy == 1 and dx in (-1, 3)),
           'hurt': -1 <= dx <= 3 and -1 <= dy <= 3 and (dx == dy or dx == 2 - dy)}[kind]
    return 'E' if hit else None

# dibuja un cuadro solo con los valores de la pose. todas las acciones usan las mismas figuras.
# squash: + achatado, - alargado. feet: pie levantado. flip: ala levantada. lean: cuerpo movido a los lados
def draw(bob=0, squash=0, feet=(0, 0), flip=(0, 0), eyes='open', mouth='', blush=False, lean=0):
    ry = (9 - squash) * S
    rx = min((13 + squash * .8) * S, 22.4)  # que el cuerpo achatado no se salga de la cuadrícula
    by = (30.4 + min(bob, 1)) * S - ry + max(bob - 1, 0) * S
    cx = min(max((16 + lean) * S, rx + 1.4), 46.6 - rx)
    wl, wr = max(cx - rx + .3 * S, 4.5), min(cx + rx - .3 * S, 43.5)
    fy = by + 1.5 * S
    ey = rnd(by - 3 * S)
    shift = 3 + u(lean)  # la cara mira 3 píxeles a la derecha
    gy = rnd((30 + min(bob, 0)) * S) - u(max(feet))

    def foot(fcx, x, y, up):
        b = 47 - u(up)
        return b - 2 + min(0, u(bob + up)) <= y <= b and abs(x + .5 - fcx) <= (3.5 if y == b else 4.8)

    def pixel(x, y):
        body = oval(x, y, cx, by, rx, ry) or tri(
            x, y, (cx - 3.5 * S, by - ry + 2 * S), (cx + 3.5 * S, by - ry + 2 * S), (cx, by - ry - .8 * S))
        fin = oval(x, y, wl, fy - flip[0] * S, 2.6 * S, 1.8 * S) or oval(x, y, wr, fy - flip[1] * S, 2.6 * S, 1.8 * S)
        paw = foot(18.5, x, y, feet[0]) or foot(31.5, x, y, feet[1])
        # pinta los pies antes del cuerpo para que queden por delante, como en un peluche
        if paw and y >= gy:
            return 'Y'
        if not body:
            return 'g' if fin else 'Y' if paw else '.'
        fx = x - shift
        e = eye(fx - 14, y - ey, eyes) or eye(fx - 31, y - ey, eyes)
        if e:
            return e
        if y in (ey + 2, ey + 3) and 21 <= fx <= 26:  # pico
            return 'Y'
        if mouth == 'open':  # al comer se abre y se ve el interior de la boca
            if ey + 4 <= y <= ey + 5 and 22 <= fx <= 25:
                return 'R'
            if y == ey + 6 and 22 <= fx <= 25:
                return 'Y'
        elif y == ey + 4 and (21 if mouth == 'chew' else 22) <= fx <= (26 if mouth == 'chew' else 25):
            return 'y'
        if blush and y in (ey + 3, ey + 4) and (11 <= fx <= 13 or 34 <= fx <= 36):
            return 'P'
        face = (oval(fx, y, 11 * S + .5, by - 2 * S, 3.8 * S, 3.6 * S)
                or oval(fx, y, 21 * S + .5, by - 2 * S, 3.8 * S, 3.6 * S)
                or oval(fx, y, 16 * S + .5, by + 3 * S, 8.2 * S, 5.2 * S))
        return 'W' if face else 'G'

    g = [[pixel(x, y) for x in range(N)] for y in range(N)]
    solid = lambda x, y: 0 <= x < N and 0 <= y < N and g[y][x] != '.'
    edge = lambda x, y: not all(solid(x + a, y + b) for a, b in ((1, 0), (-1, 0), (0, 1), (0, -1)))
    return [''.join('K' if c != '.' and edge(x, y) else c for x, c in enumerate(row)) for y, row in enumerate(g)]

ANIMS = {
    'idle': [draw(), draw(bob=1)],
    'walk': [draw(feet=(1, 0), lean=-1), draw(bob=-1), draw(feet=(0, 1), lean=1), draw(bob=-1)],
    'sleep': [draw(eyes='closed', bob=1, squash=1), draw(eyes='closed', bob=2, squash=1)],
    'poked': [draw(eyes='wide', squash=3), draw(eyes='wide')],
    'throw': [draw(flip=(0, 4), lean=-1), draw(flip=(0, 5), lean=-1, bob=-1), draw(flip=(0, -1), lean=1)],
    'hit': [draw(eyes='hurt', lean=-2, squash=1), draw(eyes='hurt', bob=1)],
    'jump': [draw(squash=3, flip=(-1, -1)), draw(bob=-3, squash=-1, feet=(2, 2), flip=(3, 3))],
    'eat': [draw(mouth='open'), draw(mouth='chew', bob=1, squash=1), draw(eyes='happy', blush=True)],
    'wave': [draw(flip=(0, 3), eyes='happy'), draw(flip=(0, 6), eyes='happy'), draw(flip=(0, 3), eyes='happy')],
    'happy': [draw(eyes='happy', blush=True, bob=-1), draw(eyes='happy', blush=True)],
    'offline': [draw(eyes='hurt', squash=3, bob=1, flip=(-1, -1))],
}

# un PNG en tira horizontal por acción (1x, cuadros de 48x48)
def save(name, frames):
    from PIL import Image, ImageColor
    img = Image.new('RGBA', (N * len(frames), N))
    for i, g in enumerate(frames):
        for y, row in enumerate(g):
            for x, c in enumerate(row):
                if c != '.':
                    img.putpixel((i * N + x, y), ImageColor.getrgb(COLORS[c]))
    img.save(name + '.png')

if __name__ == '__main__':
    for name, frames in ANIMS.items():
        save(name, frames)

Subir todo de una vez

Junta los PNG en un zip y arrástralo a la ventana de creación, o elige el zip en "Añadir en bloque", y cada casilla se llena de golpe. Si no están en zip, elige la carpeta en "Añadir en bloque". Antes de subir te muestra a qué casilla va cada archivo, y los que no coincidan en el nombre los asignas tú. Los zip de "Descargar todo" de arriba usan estos nombres, así que puedes probarlos tal cual.

Los archivos se llaman igual que la acción. No importan las mayúsculas, y puede haber carpetas dentro del zip.

Revisa antes de subir