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
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.
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.
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.
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.
Three small layers do the rest:
destination-out, each following the stroke's path at a random sideways offset, with random length, thickness and strength.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.
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.
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.
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 final boss, Mukgwi, is an ink demon, and my first version just looked like a dark silhouette. Three things made it feel wet:
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