When I was first making plans for Attack Vector, I already knew from prior experiments that sprite scaling wasn’t going to work well in software. Hardware might have been a lot more powerful in 2014 than in the days when Space Harrier saw the neon light of arcade parlors, but we’re accessing it through so many layers of software complexity that most of the difference is wasted. Oh, I could have used sprites pre-rendered into multiple sizes, but for various reasons that felt like the wrong thing to do in this case.

The next option would have been vector graphics, as I used in several games, but I soon realized it was going to take a lot of code, use proportional amounts of CPU (thus negating the advantage) and look ugly to boot. I needed some way to create my assets in advance, in a scalable format that was simple to render.

So I remembered my own tutorial, Fun with voxels. But that raised a problem.

You see, there are voxel editors out there, but using them is tedious to the point of being impractical for any model larger than a few units in each direction. And making my own before I knew exactly what I needed sounded like a recipe for derailing the project.

But then it occurred to me that I was never going to see my assets from the back, and the solution imposed itself: combine flat sprites with depth maps to create a kind of digital bas-relief.

Square sprite depicting a kind of futuristic craft in color, with a black-and-white depth map matching it pixel by pixel.

That allowed me to at least make the sprites proper with The GIMP. Not so much the depth maps, which needed a specific number of levels. Luckily, there are image formats out there that lend themselves to easy processing in a text editor. This is PGM:

P2
20 20
9
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0
0 0 2 2 0 0 3 3 3 3 3 3 3 3 0 0 2 2 0 0
0 0 3 3 0 0 3 4 4 4 4 4 4 3 0 0 3 3 0 0
0 0 4 4 0 0 3 4 5 5 5 5 4 3 0 0 4 4 0 0
0 0 5 5 0 0 3 4 5 6 6 5 4 3 0 0 5 5 0 0
5 6 7 7 8 8 8 9 9 9 9 9 9 8 8 8 7 7 6 5
5 6 7 7 8 8 8 9 9 9 9 9 9 8 8 8 7 7 6 5
5 6 7 7 8 8 8 9 9 9 9 9 9 8 8 8 7 7 6 5
5 6 7 7 8 8 8 9 9 9 9 9 9 8 8 8 7 7 6 5
5 6 7 7 8 8 8 9 9 9 9 9 9 8 8 8 7 7 6 5
0 0 0 6 6 7 7 7 8 8 8 8 7 7 7 6 6 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

As you can see, it’s very easy to parse, especially once inlined. And since I went that way, doing the same thing for the sprites was a no-brainer. The XPM format is designed for inlining from the get-go, and not much harder to decipher, either:

/* XPM */
static char * hover3_xpm[] = {
"20 20 6 1",
" 	c None",
".	c #757161",
"+	c #4E4A4E",
"@	c #6DC2CA",
"#	c #140C1C",
"$	c #8595A1",
"                    ",
"                    ",
"                    ",
"                    ",
"  .+            .+  ",
"  .+            .+  ",
"  .+   @@@@@@   .+  ",
"  .+  #@@@@@@#  .+  ",
"  .+  ##@@@@##  .+  ",
"  .+  ###@@###  .+  ",
"$.$$...$$$$$$+++..+.",
"$.$$...$$$$$$+++..+.",
"$.$$...$$$$$$+++..+.",
"$.$$...$$$$$$+++..+.",
"$.$$...$$$$$$+++..+.",
"   ++++++++++++++   ",
"                    ",
"                    ",
"                    ",
"                    "};

The end result looked better than I had any right to expect:

Screenshot from a first-person shoot'em up where futuristic vehicles exchange fire among contemporary buildings, all rendered with big colorful squares given depth.

Sadly that was as much as I could squeeze out of those tiny sprites, especially as the Python engine can only handle about a thousand voxels at the same time before the frame rate becomes unacceptable. It only works because buildings and vehicles are scaled differently, and even so, it’s a pretty harsh limitation. But the basic idea has proven itself, and with some tweaks it can be a viable alternative to heavyweight 3D rendering, at least for certain kinds of games.