Posted March 12, 2022 by Brian MacIntosh
This post is mirrored from my blog.
I just finished updating the Icon Machine with a new "spear" class for itch, and I wanted to re-share this breakdown of how the tool works for itch users.
This is a custom algorithm written specifically to produce each class of icon - not using any kind of generic machine-learning. I'm pretty happy with the amount of variety it can produce - each piece (the blade shape, the crossguard, the hilt, and the pommel) is generated from scratch from a variety of randomized parameters. So how does it work?
I usually start a procgen project by trying to draw the thing I'm making by hand. This helps me analyze how it's made - in particular, how colors combine and where shadows and highlights go. After a quick web search for "pixel art sword" and some experimentation and iteration, I came up with this:
Pretty basic, but it helped me figure out how to color the blade (a key part of the art I was unsure about) with a light half and a dark half, a gradient that starts dark at the hilt and runs up the blade, and a rim of very light pixels to hint at "sharpness".
You can follow along in the Javascript source code (drawRandomBlade function) if you'd like to see more implementation details.
Off the top, I randomize and calculate a bunch of values within ranges I set (and tweaked over time), such as:
Next, I generate the shape of the blade. I start where the top of the crossguard is going to be with a forward angle of 45 degrees, then step forward one pixel at a time. Each time, I push the point into an array, along with some metadata like the current direction, how far along the blade it is, and the width at that point (which can vary based on the cosine wave I mentioned before). After recording the point, I randomly check to see if the blade should "jog", making a sharp angle, or acquire a curve. Then, move on to the next point.
Now I actually render the blade. Walking along the control points drawing segments is intuitive, but it seemed tricky to make sure the space between each segment gets filled in properly. So instead, I actually iterate over every pixel in the image. I determine which control point is closest to that pixel, then check the distance to it. If it's within the blade's radius, we'll draw it.
Color: here's how I decide what color a blade pixel will be:
For most of these operations, I'm using linear interpolation to blend two different colors together.
It's not the same sword, sorry.
Next, it's on to the hilt. This is much simpler: I generate another cosine wave to control the lines of the grip, a width, and a color. When randomizing colors, I usually work in HSV and convert to RGB before drawing because I'm not so interested in controlling how red or green or blue the color is, but I'm much more interesting in controlling how bright (V) and how colorful (S) it is. In this case I make sure the color has a high V so it has room to get darker to make shadows.
Then I just walk along the hilt drawing slices. There is some rounding trickery to make sure I get all the pixels filled in nicely. The color of each pixel is based on the value of a cosine wave, and darkened somewhat toward the right side.
Now the crossguard. The process for this is nearly the same as that for the blade - just in a different direction. I generate two different curves for the left and right parts, but with a high probability I just discard one of them and use the other, mirrored.
Finally, the pommel. I was getting itchy to share my work at this point, so I didn't do too much here. Just generate a random radius, and draw a circle in the same color as the crossguard that's shaded darker to the bottom-right and lighter to the top-left.
Oh, and the black border is added here at the very end. Any pixel that's empty or at the edge of the canvas, and orthogonally adjacent to a filled pixel, gets filled in black.
I hope that gives you some interesting ideas! Since you made it through all that, here's a gif of the process: