Voltar para o Huggybugi
Português (Brasil)

Como criar personagens e itens

Um PNG por ação, com os quadros emendados em fila na horizontal.

Criar com a skill · o mais fácil

Envie o zip da skill para o Claude, o ChatGPT ou o Gemini junto com uma foto. Ele mostra o desenho, ajusta com você e devolve um zip pronto para usar.

Como pegar e usar a skill

Copiar a especificação e pedir

Copie esta página em markdown, cole no ChatGPT ou no Claude e ele segue a especificação.

Enviar

No menu da bandeja entre em "Personagem > Criar…", escolha a aba Personagens, Itens ou Emotes e clique em "+ Criar novo". Dê um nome (até 20 caracteres) e escolha um PNG para cada ação, ou arraste para a casinha. Os campos de fps já vêm com os valores recomendados. Nos itens você também escolhe o tipo (pra jogar, comida, cocô) e um wav de som. Ao salvar, ele aparece na sua lista, e ali mesmo você edita ou exclui. O cartão + no começo das janelas de trocar personagem, itens e emotes abre o mesmo editor.

Chega para os seus amigos quando eles abrem a janela "Personagem > Trocar…" da bandeja ou clicam em "Baixar novos personagens e itens". O personagem que você escolheu aparece na hora.

Para a comida, clique com o botão direito em um amigo, escolha uma em "Comida" e depois clique nele para jogar. Cada clique dá comida até você escolher algo "pra jogar" de novo.

No cocô, a imagem fly é desenhada como cocô no chão (não é jogada nem serve de comida). O seu personagem faz o cocô que você escolheu quando está cheio. Quem ainda não recebeu o seu cocô vê o cocô padrão até receber.

Emotes

Na aba "Emotes" escolha um GIF, WebP, APNG, PNG ou JPG. O app reduz para 64px, pega até 16 quadros e salva na velocidade original (o original, até 20MB e 2048px de lado). O nome vai até 20 caracteres e não pode repetir dentro do grupo. : e espaços não valem porque quebrariam o :nome: dentro da mensagem, e os nomes dos emotes básicos (heart·question·exclamation·angry·lol·note) já estão usados.

Onde usar: 'Emotes' no menu que aparece ao clicar no seu personagem (⌃⌥⌘E, Ctrl+Alt+E no Windows) abre a janela sobre a sua cabeça. Digite : na mensagem para ver os nomes, e ao escolher um ele entra como :nome: no balão. Com o botão de emoji da aba 'Status' da janela de status e apelido, ele vai para o seu status. No chat você também reage a uma mensagem com um. Só os amigos do mesmo grupo veem.

Especificações

Ações do personagem

Basta ter idle, walk e sleep. As outras, se faltarem, são substituídas por outra ação. Sem offline, ele vira pedra quando fica offline.

AçãoQuandoRecomendadoSe faltar
idle obrigatórioParado2 quadros, 2fps
walk obrigatórioAndando4 quadros, 6fps
sleep obrigatório30 minutos ausente ou tela bloqueada (o Zzz é o app que desenha)2 quadros, 1fps
pokedAo receber um toque2 quadros, 8fpsidle
throwAo jogar3 quadros, 10fpsidle
hitAo levar a pancada2 quadros, 8fpspoked
jumpAo desviar, pose de pulo2 quadros, 8fpsidle
eatAo receber comida, pose de nham3 quadros, 6fpshappy
waveCumprimento quando um amigo entra, pose de oi3 quadros, 6fpshappy
happyAo receber carinho, pose de feliz2 quadros, 4fpsidle
offlineOffline (caído)1 quadro, 1fpsPedra

Ações do item

AçãoQuandoRecomendadoSe faltar
fly obrigatórioEnquanto voa. Ligue 'Girar ao voar' na janela de criar e o app gira por você (nos novos vem desligado). Com o giro desligado, o que é jogado voa reto olhando para onde vai, então desenhe olhando para a direita; a comida com o giro desligado voa do jeito que você desenhou, então desenhe ela em pé. Dê movimento com vários quadros para não parecer parada (quicando, brilhando). O cocô é desenhado no chão1 quadro pra jogar, de 1 a 4 para comida, 12fps (o cocô, 1 quadro)
impactO momento do impacto (só para o que é jogado; a comida é comida na hora)3 quadros, 12fpsA explosão de estrelas que o app desenha

O wav toca no momento do impacto (no cocô, na hora que cai). Sem ele, o que é jogado e a comida não fazem som, e o cocô usa o 'pum' do app.

Exemplo: martelinho

É um item padrão. O fly é um quadro só, mas gira enquanto voa (a comida não gira).

AçãoMovimentoO que você envia (4x)
fly
Enquanto voa
Martelinho flyfly 1 quadro
impact
O momento do impacto
Martelinho impactimpact 3 quadros

Baixar no tamanho real: fly, impact, Baixar tudo (zip)

Cocô

Um item de cocô só precisa de um quadro fly. Ele é desenhado no chão, sem girar. O app não reduz nada: desenha a grade de 48×48 do jeito que está, então faça ele pequenininho embaixo no centro (o cocô padrão tem 17 de largura e 12 de altura) e deixe a parte de cima vazia. Não precisa ter cara de cocô. Qualquer coisinha que combine com o seu personagem (uma aboborinha, um caixão, um novelo de lã) serve, desde que caiba na área de 24×24 embaixo no centro. Embaixo está o cocô padrão.

AçãoMovimentoO que você envia (4x)
fly
No chão
Cocô padrão flyfly 1 quadro

Baixar no tamanho real: fly

Exemplo: Ddeoggi

Todas as ações do Ddeoggi, desenhado a partir de um pinguim de pelúcia. Usa a mesma quantidade de quadros da tabela acima e, como todas as poses reaproveitam o mesmo corpo, as ações não ficam desalinhadas entre si.

AçãoMovimentoO que você envia (4x)
idle
Parado
Ddeoggi idleidle 2 quadros
walk
Andando
Ddeoggi walkwalk 4 quadros
sleep
Dormindo
Ddeoggi sleepsleep 2 quadros
poked
Ao receber um toque
Ddeoggi pokedpoked 2 quadros
throw
Ao jogar
Ddeoggi throwthrow 3 quadros
hit
Ao levar a pancada
Ddeoggi hithit 2 quadros
jump
Ao desviar e pular
Ddeoggi jumpjump 2 quadros
eat
Ao receber comida
Ddeoggi eateat 3 quadros
wave
Cumprimento
Ddeoggi wavewave 3 quadros
happy
Carinho
Ddeoggi happyhappy 2 quadros
offline
Offline
Ddeoggi offlineoffline 1 quadro

Baixar no tamanho real: idle, walk, sleep, poked, throw, hit, jump, eat, wave, happy, offline, Baixar tudo (zip)

Modelos

Com "Baixar modelo" na janela de criar você pega um modelo com o hamster do app desenhado em cada ação (uma castanha nos itens). Os modelos vazios estão aqui: 2 quadros, 4 quadros, 8 quadros, Baixar tudo (zip)

Coloque em uma camada de baixo, desenhe por cima e desligue a camada do modelo antes de salvar. A linha vermelha marca onde ficam os pés.

Programas para desenhar: Piskel(web, de graça), LibreSprite(de graça), Aseprite(pago)

Criar com IA

As IAs de imagem não acertam os 48 pixels na mosca. Gere grande, reduza e depois ajuste.

  1. Gerar com o prompt abaixo
  2. Reduzir cada quadro para 48×48. A interpolação tem que ser vizinho mais próximo (Nearest Neighbor), senão fica borrado.
  3. Deixar o fundo transparente e alinhar os quadros

No navegador, sem instalar nada:

O visual 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

Folha de ações (mande o resultado acima como imagem de referência)

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
Ação[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

Itens

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 é jogado, desenhe um único quadro fly como o martelinho e deixe o app girar com 'Girar ao voar'. Se tiver direção, como uma flecha ou uma garra, desligue e desenhe olhando para a direita: Martelinho fly Martelinho impact

O que é jogado precisa do próprio impact (a comida e o cocô não têm):

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

Com 'Girar ao voar' desligado, a comida voa reta. Use vários quadros para dar movimento enquanto ela voa:

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

Cocô

[character] é o nome e o visual do seu personagem (por exemplo "Ddeoggi, um pinguim cinza de bico amarelo").

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

Criar com a skill (o mais fácil)

Baixe o huggybugi-character.zip, envie para o Claude, o ChatGPT ou o Gemini junto com uma foto e diga "siga o SKILL.md e crie um personagem do Huggybugi com esta foto". Ele desenha as 11 ações (e também cocô, algo pra jogar ou comida, se você pedir) e devolve um zip pronto para o 'Adicionar em lote' aqui embaixo. A IA precisa poder executar código (no Claude e no ChatGPT, use um modelo que faça isso). Se enviar todas as vezes der trabalho, registre como skill. Depois basta dar uma foto e dizer "crie um personagem do Huggybugi".

O Claude Code e o Codex instalam como plugin, sem zip, e as versões novas chegam quando você atualiza o plugin. Esses comandos e a especificação também dá para copiar em "Criar com IA" na janela de criar do app.

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

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

Os nomes dos menus mudam de vez em quando. Os detalhes estão no README.md dentro do zip.

IA que executa código (ChatGPT, Claude e afins)

A grade sai exata. Defina o corpo, os olhos e os pés com formas simples como elipses, e monte todas as ações com uma única função que receba os valores da pose (altura do corpo, forma do olho, pé levantado) e desenhe um quadro. Assim as ações nunca ficam desalinhadas. Se você der uma foto ou um desenho, ele copia as cores e o visual. Sem foto, peça para ele procurar antes alguns pixel arts fofos desse animal ou item e fica bem melhor.

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.

Os itens e o cocô têm outras ações, então use isto em vez do prompt de cima. Deixe só a linha do seu tipo, pra jogar ou comida, e apague a outra.

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.

O código de um item fica mais ou menos assim (o fly e o impact do martelinho, e o cocô padrão). As imagens do martelinho e do cocô dos exemplos acima saíram deste código. Os arquivos se chamam igual à ação, fly.png e impact.png; o exemplo abaixo desenha dois itens de uma vez, então põe um prefixo no nome.

import math

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

def oval(x, y, cx, cy, rx, ry):  # true se o centro do pixel (+0.5) cair dentro da elipse
    return ((x + 0.5 - cx) / rx) ** 2 + ((y + 0.5 - cy) / ry) ** 2 <= 1

# monta um quadro com uma letra de cor por pixel e troca a borda de fora por uma linha escura 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)]

# martelinho: um quadro fly (com 'girar ao voar' ligado, o app 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: uma estrela cresce e se parte em pedaços (3 quadros)
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

# cocô: um quadro fly. três rolos que encostam na última linha, com olhos, bochechas e boca no de baixo
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):
            # a parte de baixo de cada rolo é o encaixe que se apoia no rolo de baixo
            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)],
}

# um PNG em tira horizontal por ação (1x, quadros 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)

O código que uma IA escreve fica mais ou menos assim (o Ddeoggi, as 11 ações). As imagens do Ddeoggi dos exemplos acima saíram deste código. Se você tem o Pillow, basta executar e um PNG é salvo por ação.

import math

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

def rnd(v):  # arredonda 0.5 para cima (o round do python vai para o par, então não se usa)
    return math.floor(v + .5)

def u(v):  # os deslocamentos da pose, em pixels inteiros da grade 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)

# um olho (base 3x3). dx, dy contam a partir do canto superior esquerdo do olho
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':  # surpresa: 4x4 com um brilho
        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

# desenha um quadro só com os valores da pose. todas as ações usam as mesmas formas.
# squash: + achatado, - comprido. feet: pé levantado. flip: asa levantada. lean: corpo movido para os 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)  # para o corpo achatado não sair da grade
    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)  # o rosto olha 3 pixels para a direita
    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 os pés antes do corpo para eles ficarem na frente, como numa pelúcia
        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:  # bico
            return 'Y'
        if mouth == 'open':  # ao comer ele abre e dá para ver dentro da 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))],
}

# um PNG em tira horizontal por ação (1x, quadros 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)

Adicionar em lote

Junte os PNG em um zip e arraste para a janela de criar, ou escolha o zip em "Adicionar em lote", e cada casinha é preenchida de uma vez. Se não estiverem em zip, escolha a pasta em "Adicionar em lote". Antes de enviar ele mostra para qual casinha vai cada arquivo, e os que não combinarem no nome você escolhe na mão. Os zip de "Baixar tudo" de cima usam esses nomes, então dá para testar do jeito que estão.

Os arquivos se chamam igual à ação. Maiúsculas não importam, e pode ter pasta dentro do zip.

Confira antes de enviar