Boreumdal — a one-person studio · 한국어

Painting ink with code

Every brush stroke, ink blot and sheet of hanji paper in a small mobile parry game is drawn by code at load time. Paint with the game's own brush below.

Boreumdal (FullMoon), solo developer · October 2026

An ink swordsman inside a brushed ink circle, a boss in black ink above, ink splashes on warm hanji paper
Ink Parry: Seonbi Swordsman. The ring, the swordsman, the boss, the arrows and the paper are all generated.

Ink Parry: Seonbi Swordsman is a parry game set in Joseon-era Korea, drawn to look like ink on hanji. I'm not an illustrator, and I wanted dozens of attacks, bosses, swords and backgrounds in one consistent hand. So the game ships with no image files. Everything is painted onto canvases with a single brush function when the game loads, then handed to PixiJS as textures. The whole iOS app is 13 MB.

The soundtrack is generated too; I wrote that up in A gayageum with no audio files. This post is about the pictures.

Paint with the game's brush

Drag on the paper to draw. This is the stroke function from the game, running in your browser. The sliders change the same options the game uses.

1. A stroke is a shape, not a line

Canvas lines have the same width from start to finish, and that's the first thing that gives away "computer drawing". A real brush lands, presses, and lifts off. So a stroke in the game is never a lineTo with a lineWidth. It's a filled polygon built around a path:

const widthAt = (u) => {             // u: 0 at the start, 1 at the end
  let k = 1;
  if (u < taperStart) k = startWidth + (1 - startWidth) * smoothstep(u / taperStart);
  else if (u > 1 - taperEnd) k = endWidth + (1 - endWidth) * smoothstep((1 - u) / taperEnd);
  return width * k;
};

Set Taper to zero in the demo and you get the uniform canvas line back. The difference is most of the look.

2. Edges that aren't quite straight

A perfect tapered shape still looks vector-clean. Paper fibers and hairs in the brush make the edges waver. Each edge gets its own 1D value noise (random values on a grid, smoothstep between them), sampled along the stroke's length so the waver has a size of about 6 px no matter how long the stroke is. It moves each edge by up to 8% of the width. Because the two sides use different noise, the stroke never looks like a mirrored shape.

3. Bleed, pooling, and the dry brush

Three small layers do the rest:

The carving happens on a scratch canvas, which is then drawn onto the target, so the gaps cut only the new stroke and never what's already painted underneath. Turn Dry brush up in the demo to see the streaks; turn it to zero for a wet, solid stroke.

4. Blots and splatters

Ink drops are polygons too: 22 points around a circle whose radius is pushed in and out by noise, drawn twice, a faint halo slightly larger and then the solid drop. A splatter is one blot plus seven to eleven smaller drops and streaks flung outward, used for the ink splashes in fights.

A battlefield duel, with ink-brushed attacks and splatters Boon cards with brush-drawn icons

5. Bake once, in white, then tint

Drawing all of this every frame would be far too slow on a phone. Everything is painted once while the game loads, onto offscreen canvases, and turned into PixiJS textures. Most of them are painted in white. At runtime a sprite's tint turns the same texture into ink black, seal-red vermilion, pale wash or gold, so one baked shape can be an attack in ink, a warning in vermilion or a reward in gold.

Each piece of art has its own seeded random generator (a small sfc32). That makes every drawing identical on every device and every launch, and it means adding a new sword doesn't shift the random numbers of the bosses drawn after it, which would quietly redraw them.

6. Paper that stays out of the way

The hanji background is generated the same way: fiber noise, a soft vignette, and three layers of distant mountains from three octaves of value noise, fading into mist toward the bottom. A dry-brush stroke traces the nearest ridgelines. There are also scroll paintings you can unlock (plum branch, river crossing, palace wall, pine, waterfall, bamboo, moon, snow), all painted in code and kept out of the lanes attacks travel through.

The rule that mattered most is contrast. Even the darkest mountain is only about 1.3:1 against the paper, warning marks are 5.6:1, and attacks are 14:1. In a game about reacting in a fraction of a second, the background is never allowed to look like a threat.

The title screen: hanji paper with layered ink mountains The ink demon Mukgwi with a tide of ink

7. A boss made of wet ink

The final boss, Mukgwi, is an ink demon, and my first version just looked like a dark silhouette. Three things made it feel wet:

What I learned

Ink Parry: Seonbi Swordsman is out on iPhone and Android. It's a one-time purchase, with no ads and no in-app purchases.

See the game · App Store · Google Play · How the sound works