Making a Modern Vector Graphics Game
by Michael Klaus Schmidt
For many years, pixel graphics have been the style of choice for many games. From the earliest days of video games, large pixels were the format of choice (and necessity) for many games, and this style has worked its way into the psyche of many players. When “retro” video games became a thing of importance, it was natural that they would choose to use large pixels for their graphic assets and art.
Most game engines have ready made systems to use 2D sprites, and making games “pixel perfect” is no longer as difficult as it once was. This makes it a perfect choice for smaller scale developers and studios who do not have the time or budget to make high resolution, realistic models.
This screenshot is from one of my favorite pixel art games "Lovecraft's Untold Stories"
However, there is another completely different graphic style, that may actually pre-date pixel graphics, that has gone largely unacknowledged in recent years. Vector Graphics games like Battlezone, Asteroids, Tempest, Tailgunner and even the very first Star Wars game used to dominate the arcades in the early 1980's. In fact, these games were the precursors to modern 3D games, using vectors to draw lines in a virtual 3D space.
So, for me, it seems natural that this style would also make a comeback. However, unlike 2D sprites, most game engines do not have a ready-made method of producing vector images. This is a problem, because without this kind of easy access, the trend, and potential swarm of new games, will be stunted before it gets a chance to take off and reach its full potential.
So I have come up with a method of producing vector graphics in a modern game engine. Now, before I begin explaining how to approach this, I first would like to say that there are probably other, equally valid, potentially better ways to do this. This is the method I used, but that should not limit anyone from exploring their own preferred way of doing things.
Second, I am not using one of the more popular game engines, so while my overall approach should work regardless of the engine used, the specific scripts I'm using will not.
Note: This method describes how to make vector games for standard graphics displays. This is not the same way vector games were created originally, which used oscilloscopes, or other specific pieces of graphic hardware, to generate true vector graphics.
Here is a screen capture of my latest creature in "Paradox Vector"
To generate vector graphics on screen, we really need to focus on two main points. The first is the 3D models. Originally, vector games probably did not have “models” in the modern sense, and objects would have been generated through some kind of script. This is just speculation though, as I never learned how vector games were made in the early days of game development.
For modern engines, we generally use models to render 3D objects, and I thought this would be the best way to do it. However, our models will need to be created in a way that is different from those of most games. The main difference is that we do not use textures to render the model/mesh. I actually do use a texture, but it is solid black, and therefore cannot really be seen in-game.
So, I start by making a low-poly model of my game object, just as is done in any game. In theory, the models do not need to be low poly. I really think there could be some very unique things one could do with higher resolution models done in vectors, since that is something I've never seen before, but for now, and for the sake of simplicity, we will stick with low poly models.
This is the 3D model I used to generate my "Old One."
Once the model is done, I have to think about how I want to draw it. Since the model will not be visible, being solid black against a black background, I have to think about where each vector will be located and how it should connect to other vectors, i.e. where the lines will be drawn.
Why not just use the existing vectors in the model you ask? Well, the way I am rendering these models, it will connect each vector to the vector that was made right after it, in my game engine (3D GameStudio) vectors are numbered. So, in order to render correctly, each vector has to be produced in the order in which the lines will be drawn. Also, if the vector is also used to make a face for the model, if that face is solid (occluding shapes drawn behind it) then it will also occlude the lines being drawn, and they may appear imperfect.
So, to avoid these problems, I came up with the following method. Starting with my basic model, I then start adding new vertices carefully, one after another, just at those points where I want a line to be drawn. I will add them just outside the original “structural” vertices of the model, so the lines are not cut off by the faces of the mesh.
Another important aspect to this is that I am not connecting these “drawing vectors” in the model in any way. They are going to be used by the script to draw this object, but they should not be used to form faces. Rather they will be “floating” just above the surface of the model.
This is a close-up of the same model, showing the "floating vectors."
Also, since you do have a solid, black object to work with, you can also bring some of the vectors inside of that object. These inside lines will not be drawn, but this can be used as a way to re-position the line without having to create a complicated script that stops the line from being drawn between certain vectors. Of course, that would be another potential way to handle doing this.
Using Line Draw
So the main tool we will use to render our 3D scenes is called a line-draw command. What I am doing is taking each game object, and for each frame, calling a script that draws all the lines that render the object. First, for each model, I will need to keep track of the number of the vertices used for rendering. So, say my base model uses 30 vertices for all the solid faces. Vertex number 31 will be the first “floating” vertex used for drawing. In my script for that object, I will use that number, and send it along to the drawing script, along with other important things like color and alpha.
The drawing script takes the information sent by the object, and uses it to render each line with a line-draw command. So it creates a while loop. In 3D Gamestudio, which uses the Lite-c language, loops can work one of two ways. If you put a wait(1); command at the end of the loop, it will run through the loop once for each frame. This is useful in most cases, because you don't want the game to get caught in the loop, thereby freezing the game. However, when you know the loop will be running a finite number of times, it is also acceptable to leave out the wait(1); command. This will cause the script to run through each iteration of the loop without waiting until the next frame, and this is just what we will do here. Depending on your game engine and/or scripting language, this may be handled differently, so you will need to look into that.
Now in my script, it looks at the first vector I want to draw, and starts there. Then, it increments to the next vector, and draws a line between the first and second. Then, it draws a line between the second and third, and so on until it reaches the last vector. Then, when there are no more vectors to draw, the loop ends and it moves on to the next part of the script.
Here is a breakdown on how the script works. I'm using the same lines as those in the code shown above to describe what it's actually doing. With this, you may be able to convert this script into the language of your choice.
769 Declaration of the functions and its parameters, which include the three RGB colors it will be drawn with, its alpha (transparency) and the number of the starting vector. This function will be called by the game object being drawn once per frame, and it will look like this "draw_me(0,0,255,100,31);" In this example, it will draw a red game object, with no transparency, starting from the 31st vector of the model.
771 Declaring a Vector (set of three variables x,y,z) called line_pos
772 Declaring a Variable called line_x and initializing it to the start_vec position
773 Declaring a Variable called vert_num which is assigned "ent_status(my,1)" which returns the total number of vertices in the given model.
775 Starting a while loop, which will continue until the last vertex (vert_num) is reached
777 Assigns the position (x,y,z) of the current vertex to the Vector line_pos
779 if this is the very first line being drawn...
781 draw the line but do not assign any color to it. This will position the line at the first vertex, but will prevent a line from being drawn between it and the last object that was drawn. This is a particular feature of the game engine being used, and may or may not be necessary in other engines.
783 if this is the last vertex in the mesh...
785 It assigns the position of line_pos back to the first drawn vertex. This is another thing particular to the way I designed my meshes. I tried to add the last vertex right over the very first one, so I would have a sort of closed mesh. There are other ways to approach this, and you may want to allow for open meshes, so this would not be a necessary step.
790 Draw the line from the end of the last line drawn to the current vertex position
793 Increment to the next vertex position
796 Reset the currently selected vertex position. I believe this step is unnecessary, and I'm not exactly sure why I added it to my script. However, since the script works right now, I will not worry about fixing/changing it :)
This is not exactly a built in way to generate vector graphics. However, it does work, and it is not much more complex than designing any other aspect of game graphics. If carefully placing vectors in order seems time consuming, well, it is, but not much more so than generating UVs and texture maps.
Perhaps, some day, a game engine designer will come up with a more straightforward way to do this. Certainly, there are shaders that can be used to create somewhat similar effects, but they do not quite capture the original simplicity of vector graphics. Until then, I hope maybe some other developers might want to try this out. With modern computers, I think the potential to generate interesting and complex vector games has a huge creative potential that is largely untapped.
As a stylistic approach, vector graphics have the same potential nostalgia as pixel graphics, especially for older gamers. They say limitations are the source of creativity, and by limiting the way we render a game could generate whole new ways of thinking about game graphics.
This is a waterfall scene from "Paradox Vector". Like the impossible artwork of M.C. Escher, this waterfall flows around and feeds itself!