Posted June 18, 2021 by dukope
Most of the work on this game so far has been figuring out how to make interesting-looking martians. I've been prototyping this in Python, writing a script that combines individual facial features with a head shape and some extra stuff. None of this is very complicated but that rarely stops me from posting about it.
Right now there are 8 main types of facial features: ear, eyebrows, eye, hair, hat, horn, mouth, and nose. I thought I'd get my drawing arm limbered up for this and wanted lots of different variations of each type. Instead of using a tablet for sketching this time, I printed a light cyan grid on paper and drew a bunch by hand.
After scanning these in, the script breaks the page into cells, thresholds the green channel to ignore the cyan lines, and extracts the individual features.
One extra step is that I can edit the scanned image before the script processes it to add magenta lines where I want closed areas. This gives me basic control over the transparency channel.
The script then builds a face by combining a few features together into a simple stacked set of rows. Each row is one or more of the selected feature, randomly rotated/scaled slightly.
Eyes, a nose, and a mouth, stacked
The mouths are treated specially with basic curves to get some more variation.
Sad! Sad? Happy! Happy?
And once the eyes are set an eyebrow row can be added on top, also stroked along curves for variation.
Megaunibrow
Certain regions of the original feature grid cell are marked as non-solid, which allows snuggling the rows together vertically with natural overlap.
I played around with different types, repeats, and orders of the rows but since I'm mostly drawing human-looking features, not much beyond an eyes-nose-mouth stack makes much sense or looks very good.
Just changing the number of features per row, and collapsing some rows can give interesting results though.
Ok, nothing too special happening so far. Moving on.
Now that we've got some junk on the face, it'd be nice if there was a skull to hold it all. You could do a good job of this by just drawing lots of variations and dropping the facial features onto them. If I was less lazy and more talented that's what I'd do. Fortunately my alternative is interesting enough for some devlog details.
The first step was deciding that I'd use splines to define the face shape, specifically bezier curves. Of all the options, beziers are a bit of a pain to work with -- each control point requires two tangent handles and editing tools have complex finicky ways of dealing with these.
Bezier path editing in Illustrator
Still, I think beziers look the best when done well and if you want an easy way out like me then you need an algorithm which automatically generates smooth tangent handles from a given set of control points. I adapted a bit of code I used on Obra Dinn for this. Editing bezier curves in 2D is no fun but in 3D it's absolutely tortuous so this technique was necessary to keep my sanity and give me nice smooth curves on the ghost smoke trails and tentacles in that game. Instead of free positioning on every handle for every point, there's a general "smoothness" setting that affects the whole path.
Automatically generated bezier handles with increasing smoothness
Ok so just port that code, slap a path around the head and we're done.
Our good friend, Eggy
Hmm. I'll deal with the overall shape more below but first, surely the stroke could be more interesting than just a solid line. I went back to pen and paper and drew a few textured balls.
A few textured balls
Load these in and unroll them into straight lines.
Ball edges unrolled into straight lines
Then, instead of stroking a solid line, the script chooses a small segment that matches the desired tangent of the curve and splats that down, repeating the process over and over along the path.
Splatting the texture segments along the curve
Depending on how complicated the texture ball is the result can be a little disjointed where the splats don't line up, but I don't mind it. The ease of adding new textures by just drawing a ball is hard to beat.
That mostly takes care of spicing the line up, now to fix the egg shape.
A round enclosing path is a good start and a bad finish. I first tried jostling the control points around to make a more interesting shape but that was underwhelming. Eventually I worked out a fairly simple system:
1 // Divide the face vertically into a top and bottom.
2 // Apply one of a few custom path generators to translate and enclose the top/bottom rectangles in different ways.
BEAN . BOOT . FLOP
Best part of these path generation algorithms is their names in the script. I've got five shape generators right now and plan on adding more. The top/bottom split point can be randomized, along with the forehead, chin, padding, and other spacings.
At some point I got the feeling that the features I'd drawn were a little too simple, hitting the limit of my ink-on-paper skills. The next step was to go digital so I took the scans into Procreate and redrew them with much more detail.
Along the way I also buffed up the script to procedurally add features around the outside, some basic shadows, extra internal border segments to define rough shading, various blemishs, and thick outlines to the border.
Your favorite childhood cartoon, reimagined for today's streaming series
Yikes. The stark face of working on something, getting tired of what you're looking at, and fiddling with it forever. No doubt I'll exhaust myself before this thing is done.
All of the features, shapes, and other bits of the face generation are driven through a simple probability system that lets me control how rare or common certain configurations are.
Some configurations, like a missing nose, are rare
One thing I haven't fully decided is if I'll port this Python script code to C and try to run it on-device. The alternative is to pre-generate the faces as part of the build process and just load/use those in the game. Since I don't have the exact gameplay nailed down I don't need to figure this out yet.