November 10th, 2026

How to make a pixel art inventory UI with 9-slice and rarity effects

How a small pixel art loot window was made. 20 generated pieces at exact sizes, a 9-sliced frame, rarity slots drawn in code, a staggered reveal with an effect per rarity, a glow behind the window and cartoony sound effects. With the prompts and Python code.

A pixel art loot window with a wooden frame and 15 slots on a stone floor, 12 items inside with blue, green, purple and gold rarity borders, a flaming legendary sword with a tooltip, and a warm glow behind the window

A chest shakes, pops open, and a loot window fills up one item at a time. Each item lands in the colour of its rarity, the epic ones shimmer, and the legendary sword gets a little show of its own.

The art is 20 small pieces. The slots, rarity colours, effects, glow and timing are all code. Here's how each part was made.

The art is 20 pieces at their exact size

All the generated pixel art for the loot window on a checkerboard: 15 item icons at 32 by 32, a 96 by 96 wooden window frame with brass corners, a wooden title plate, a gold button, a treasure chest and a leather backpack

Every piece came from Pixel Engine Image, which draws pixel art at the exact width and height you ask for, with the background already removed. Each call used the same wording around the item (pixel art, crisp 1 px dark outline, top-left light), so the frame, the chest and the icons read as one set.

  • Window frame, 96 x 96
  • Title plate, 64 x 18
  • Button, 80 x 20
  • Chest, 48 x 40, plus an 8-frame opening animation
  • Backpack, 32 x 32
  • 15 icons, 32 x 32 each (12 in the loot, 3 spares)

Make the icons one at a time

The first try was one image with all 15 icons in a grid, in a smoother style before I switched to pixel art. They look like a set, but the sword fills its whole cell and the ring is tiny. In a slot grid that's the first thing you notice.

Left, one generated image with 15 item icons where the sword and staff are long and the ring, gems and coins are small. Right, the same 15 items generated one at a time at 32 by 32 pixels, each filling its square cell the same way

So each icon got its own generation at exactly 32 x 32, with the same wording around the item. The fill rule keeps the sizes even. Long items go corner to corner, round items fill the canvas with a 1 or 2 px margin.

A legendary flaming longsword RPG inventory icon: gold crossguard, ember-orange glowing blade, drawn diagonally from the bottom-left corner to the top-right corner, filling the canvas with a 1 px margin. Pixel art, crisp 1 px dark outline, top-left light, no background.
A rare steel knight helmet RPG inventory icon, front three-quarter view with a dark visor slit and a small blue plume, large and centered, filling the canvas with a 1 px margin. Pixel art, crisp 1 px dark outline, top-left light, no background.

Ask for two variations and keep the cleaner one. The rarity word in the prompt (a rare helmet, an epic ring) nudges how fancy the item looks. The rarity colour comes from the slot, so one icon works at any rarity.

9-slice the frame

The frame is one 96 x 96 piece. Cut it 16 px in from each edge and you get 9 parts: 4 corners, 4 edges and the middle. The corners stay as drawn, and the edges and middle repeat until the window is the size you need.

The 96 by 96 pixel art wooden frame with dashed slice lines 16 pixels in from each edge, and the 222 by 172 window made from it, with the same corners and the edges repeated

Repeat the edges, don't stretch them. On pixel art a stretched edge turns rivets into bars and smears the wood grain.

The top edge of the wooden window twice. Stretched, a single rivet becomes a long gold bar and the wood grain is pulled flat. Repeated, the rivets stay round and evenly spaced

In Godot, that's a NinePatchRect with all four patch margins at 16 and both axis stretch modes on Tile. In Unity, set a 16 px border on the sprite in the Sprite Editor and use the Image type Tiled. Drawing it yourself looks like this:

def nine_slice(src, w, h, b=16):
    """Corners as drawn, edges and middle repeated (never stretched), so every pixel stays the same size."""
    sw, sh = src.size
    src_cols, src_rows = [(0, b), (b, sw - b), (sw - b, sw)], [(0, b), (b, sh - b), (sh - b, sh)]
    out_cols, out_rows = [(0, b), (b, w - b), (w - b, w)], [(0, b), (b, h - b), (h - b, h)]
    out = Image.new("RGBA", (w, h))
    for (sy0, sy1), (y0, y1) in zip(src_rows, out_rows):
        for (sx0, sx1), (x0, x1) in zip(src_cols, out_cols):
            piece = src.crop((sx0, sy0, sx1, sy1))
            for y in range(y0, y1, piece.height):
                for x in range(x0, x1, piece.width):
                    part = piece.crop((0, 0, min(piece.width, x1 - x), min(piece.height, y1 - y)))
                    out.alpha_composite(part, (x, y))
    return out

Draw the whole UI at its real pixel size, then scale it up by a whole number with nearest neighbour (4x here). That way every pixel stays a clean square.

Slots and rarity, drawn in code

Each slot is a 34 x 34 box. It has a border in the rarity colour, a dark fill, a dark inner line along the top and left, a lighter one along the bottom and right, and a small notch in the corner for anything above common.

Five pixel art inventory slots side by side, common grey with a dagger, uncommon green with boots, rare blue with a helmet, epic purple with a ring and legendary gold with a flaming sword
RarityBorderLightFill
Common#5e6878#8b96a6#26212e
Uncommon#3a7d3a#6cbf4a#1c2a22
Rare#2f6fc4#64b3f0#1b2440
Epic#6d3aa8#a974e0#261c3a
Legendary#d9a440#f2d16b#33261a

The light colour is for the effects: rings, sparkles and the light running round the border. Stack counts sit in the bottom right corner as tiny 3 x 5 pixel digits with a dark outline.

The reveal

  • The window appears at its real size, with a fade and a 6 px rise over 0.22 s. Stretching the finished window open makes the wooden frame look like rubber.
  • Every slot starts grey. One item lands every 0.15 s. Its slot takes the rarity colour on the same frame, and the icon drops in from 3 px above with a tiny bounce.
  • Each rarity plays its own effect the moment it lands:
    • Common, nothing extra
    • Uncommon, 4 small sparkles
    • Rare, a ring that grows 3 px out from the slot and 6 sparkles
    • Epic, a ring that grows 5 px and 8 sparkles, then a short streak of light running round the border every 1.4 s and a soft glow that breathes
    • Legendary, a 2-frame white flash, light rays and a beam behind the slot, 14 sparkles, a shine sweeping across the blade every 1.4 s and a gold light running round its border
  • Only the legendary flashes white. With 12 items landing, a flash on every item was too much going on.

The sparkles are 4-point pixel stars that shrink from 7 px to a single pixel. The rays are dithered, so they stay pixel art. They're drawn on top of the neighbouring slots for about half a second, then calm down to a sparse dot pattern.

The glow behind the window

Four frames of the loot window appearing. First the window fades in over the open chest with no glow, then a warm glow bursts out past the frame with soft light shafts, then it shrinks back, and finally a calm warm glow sits around the window

The glow starts 0.1 s after the window. It bursts out past the frame in under 0.2 s, settles back to about half that strength by 0.8 s, and then breathes slowly. Seven soft light shafts turn on top of it, so it never sits completely still.

A blurred copy of the window's rectangle looked boxy, so the glow is round. It's based on the distance from the window's centre, measured on an ellipse through the frame.

def glow_shape(tau, t):
    """(reach, strength, shafts) tau seconds after the glow starts, t = clock time for the slow breathing."""
    breathe = math.sin(2 * math.pi * t / 2.6)
    calm = (0.6 * (1 + 0.06 * breathe), 0.5 * (1 + 0.12 * breathe), 0.35)
    peak = (0.95, 0.9, 0.85)
    if tau <= 0:
        return 0.0, 0.0, 0.0
    if tau < 0.18:                                        # burst out past the frame
        e = 1 - (1 - tau / 0.18) ** 3
        return tuple(p * e for p in peak)
    e = smoothstep((tau - 0.18) / 0.62)                   # settle back
    return tuple(p + (c - p) * e for p, c in zip(peak, calm))


def add_glow(floor, r, a, tau, t):
    """floor: HxWx3 float array behind the window. r: distance from the window's centre, 1 on an ellipse through the
    frame. a: angle around that centre. Adds a round warm glow plus 7 soft light shafts turning slowly on top."""
    reach, k, ks = glow_shape(tau, t)
    d = np.maximum(r - 0.55, 0)
    glow = np.exp(-d * d / (2 * reach * reach))
    wide = np.exp(-d * d / (2 * (1.45 * reach) ** 2))     # the shafts reach a bit further than the glow
    shafts = (0.5 + 0.5 * np.cos(7 * (a - 0.18 * t))) ** 3
    light = glow[..., None] * np.array([255, 178, 84]) * (0.5 * k) \
        + (wide * shafts)[..., None] * np.array([255, 218, 140]) * (0.32 * ks)
    return floor + np.minimum(light, 255) * (1 - floor / 255)   # screen blend: no flat, clipped yellow

Adding the light straight onto the floor clipped to flat yellow at the peak. The screen blend on the last line keeps the stone texture showing through.

The chest shakes once, then opens

The chest rattles once for 0.6 s and builds as it goes, from 1 px to 3 px side to side with small bounces. It stops for a beat, then the lid opens with a 4 px hop and gold specks fly out and fall back. Three short shakes in a row felt like stalling.

The lid is an 8-frame animation of the chest sprite, made with Pixel Engine v1.5 from one prompt:

The treasure chest's domed lid swings open upward on its back hinges in one smooth motion and a warm golden glow shines up from inside. The chest body stays still. It ends fully open.

Sound effects

The sounds come from ElevenLabs' sound effects model. Two things made them a lot better.

Describe the sound itself. A prompt like "inventory open" names an event, so you get something generic. Write what you'd hear, like a wooden lid creaking and then slamming open.

Say cartoony. It keeps the sounds in the same world as the pixel art.

MomentPrompt
Chest shakescartoony treasure chest shaking hard, wooden lid clattering, metal latch rattling and coins jingling inside, building up
Lid opensheavy cartoony wooden treasure chest lid creaking then slamming wide open with a deep solid wooden thunk
Under the reveal, 3.5 scartoony RPG treasure chest opening chime, long magical harp glissando with twinkling bells and a warm shimmering pad, rising and sparkling
Metal item landscartoony RPG item pickup, short metallic armor and blade clink
Potion landscartoony RPG potion pickup, short glass bottle clink with a little slosh
Coins landcartoony RPG coins pickup, short bright coin jingle
Rare dropcartoony RPG rare item drop, crisp crystal bell ding with a shimmering magic tail
Epic dropcartoony RPG epic item drop, magical rising chord of glassy bells with a soft whoosh and bright sparkles
Legendary dropcartoony RPG legendary item drop, powerful golden choir hit with bright ringing bells, shimmering magic and a deep soft boom
Tooltipsoft video game menu hover sound, a gentle warm blip

Each item plays the pickup sound of what it's made of (metal, glass, wood, leaves, coins, gems, leather or paper), with its rarity's drop sound on top. Every item gets a slightly different pitch, so 12 landings in a row don't sound copy-pasted. The long chime starts with the lid and runs under the whole reveal.

Make 3 takes of each sound and listen. A few takes came back almost silent, so check the level before you use one.

Where to next