Dec 19 2010

BattleWire16K Design Notes

BattleWire16K is my entry for the 8bitRocket 16kb Atari Inspired Retro Re-Make contest. I made several blog entries during the development, here is a list of all the previous ones.

This entry is my notes on project completion. As it will run somewhat long, I’m hiding it behind a ‘Read More’ tag. I’ll add a link to the actual game on Monday when it’s available publicly.

General & Limitations

The object of the contest was to write a web game, inspired by an Atari game, which had an executable of 16kb or less. Basically any plugin was allowed, I chose to use Flash, because that’s what I’m used to working in.

In Flash, 16kb is small… but it’s not too small. There had been a fairly well known 4k Flash Game competition in 2009, in which several developers entered compelling games. At that scale, basically every game asset has to be produced in code, and the code itself needs to be optimized severely for build size rather than performance.

At 16kb, embedded images and sounds are an option. I didn’t really explore what could be done with minimal bitmaps… some brainstorming led me from the idea of writing my own bitmap file format where each binary digit represented the on/off state of a pixel, and drawing the images one pixel at a time with the drawing API.* Then I decided that if I was going to use the drawing API, I might as well do the game as vector graphics. And after doing some math, I decided it was possible to do wireframe 3d*****.

Defining the Space, Rendering the object

3d spaces are defined by a coordinate system. Looking at your monitor, the most intuitive way to define 3-space would be to say: “X is the horizontal, increasing from left to right. Y is the vertical, increasing from bottom to top. Z is the depth, increasing from the plane of the monitor into the screen.” Flash, in it’s screen coordinates, places the origin of the 2D screen at the top left. X increases left to right, but Y increases from top to bottom. To stay consistent with Flash, I set my 3-space Y axis to increase from top to bottom. This gave me a left-handed coordinate system by accident…

Fundamentally 3d images in Flash are being presented on 2d surfaces (our monitors!). If we pretend our eye, or camera is a point in the 3-d world, our monitor is a plane at some arbitrary orientation in that world, and we draw a line from every point in the world through the camera… where the line intersects the plane is where the point would appear on the screen. So I worked out the math and wrote a wireframe engine to test it. This required creating two classes, one to hold model data, and one to actually take that data and display it. See Wikipedia for an explanation.

That took time… and when I was done I didn’t get a rendering of my test cube, I got a white point at the center of the screen. I initially thought I had made a mistake with the drawing API (almost everything else I’ve done has used bitmap graphics exclusively). In fact… I had set my ‘camera’ point 1 unit away from my ‘monitor plane’. (This made one of the arctan calculations simpler (on paper), because the adjacent side was always 1… the computer doesn’t really benefit from this however.) If the model was any reasonable distance from the plane and in-line with the camera, all the lines intersected at effectively the same point. I don’t recall exactly how I made that realization.

Probably the correct fix would have been to move the camera further back, narrowing the field of view but spacing out the lines.*** Instead I applied a magnification factor to the screen x,y points which my trigonometry had generated. (This also effectively reduced the field of view) I settled on my magnification factor basically by cut-and-try.

Moving the Object

Now that I could draw my model cube, I needed to be able to move it around or else I wouldn’t be making much of a game! After implementing a moving camera (which could move forward and backward, and rotate left and right), I found that when the cube was near the edge of the screen it started to become distorted. This was fixed by narrowing the field of view still further with the magnification factor until the distortion was minimal. The other problem was the nonsensical lines drawn when the cube was located outside the viewable area. This was handled by flagging points which were calculated to be off screen by more than a few pixels and not drawing lines to them.

The basic data structures

The model data class consisted primarily of four arrays…

  • Model (reference) verticies, which define the model
  • Global verticies, which represent the model at it’s location in 3-space
  • Screen verticies, which may be drawn or not drawn
  • Edges a list of vertex indicies which need to be connected together.

The model class had methods to…

  • Translate the reference verticies to global verticies
  • Translate the global verticies to screen verticies, given a camera

The MainView class consisted primarily of a method which could be expressed most simply like this: “For each edge, if both screen verticies are visible, draw a line between them.”

Eventually the model class gained properties for line color and thickness, a collision radius, and one or two other items but originally everything was rendered 2px wide and white. The model class is never instantiated directly… all of the other objects inherit from it– they were all wireframe objects, and all render the same way, so once I had it working, why change it? And once it worked, if I needed to change something this let me change it in one place instead of several.

So at this point we can render a cube… we can move it around. Now what do we do with it?

Battlezone and the Controls

I don’t know how exactly I decided to use my nascent wireframe rendered to do a Battlezone like game, rather than say, a flight sim. (Actually, probably I subconsciously considered a flight sim more complex than a tank game, although I ended up needing 3 axis rotation anyways…)

I’m slightly too young to have played the original Battlezone Arcade machine. I know I played the (much) later Arctic Fox (Dynamix), and I probably played the Atari 5200 version which used sprites rather than computing the wireframes. In researching the game, I learned that Battlezone (the original) used two joysticks which controlled the speed of the players tank treads.

Frankly, there was no way I was going to emulate that control scheme in Flash.*** But I did want two-handed controls. The reasonable thing to do was to allow the turret to pitch up and down, and rotate left and right independent of the tank. This meant the user needed to use two sets of directional controls (WASD+IJKL). IJKL is a retro staple as well, so it fitted the theme.

In retrospect, I possibly could/should have implemented the turret movement as mouse-look.

Collision Detection

I used spherical collision detection, which is circular collision detection in 3d. If the distance between the center of two spheres is less than the sum of the radius the spheres intersect. There’s really not much to say about this. Because the objects are not spheres themselves it’s possible to shoot through some model edges and not register a hit, or miss slightly and register a hit.

Enemy Behavior

I should confess that I’m -terrible- at Battlezone variants! Before adding the terrain, if I let the tank turn directly toward the player and shoot, I’d last only a couple of tanks and certainly wouldn’t be able to test multiple tanks. So I made the tanks stop short in their rotation. (Each tank, when it is created has a random offset from 1-26 degrees… the 1 degree tanks will kill the player at medium range) I also made them slower than the player.

Of course later in the development, I got pretty decent at my own game. I increased the accuracy of tank rotation slightly (originally it was 2-28 degrees), but I was concerned that I knew the game too well myself to make substantial increases in difficulty.

Oddly, between the controls and the forgiving enemy behavior, the few non-gamers who saw the game seemed to find it fun and not overly difficult to get into. Obviously I don’t know as of this writing whether gamers will accept it.

Terrain

The development of the terrain followed the following course:

Horizon only

This involved drawing a horizontal line, so there’s not much to discuss. :)

Terrain of varying height

The terrain gets randomly generated at the start of the game. Each point is assigned a height (y coordinate), and set 500×500 point grid. The terrain model grabs all the points +-20 x and z from the player, and treats them as a model– same as a tank or airplane.

The problem is, how do you know how high an arbitrary point between terrain checkpoints is? The solution is to use the fact that three points define a plane. By taking the nearest three points, and drawing vectors from one to the other two, we can use the cross product of those vectors to get an equation for the plane. A good explanation can be found here.

Using that equation, we can get the height of terrain at a given global x an z. I set the pitch and roll of the tanks by checking the height of both the center of the tank and a point 1 unit in the appropriate direction. Using the arc-tangent, we get the angles.

Deformable Terrain

This came about because I realized there was no reason not to. The points of the terrain model are constantly changing as the player moves anyways. I happen to really like this feature.

Color

With the Terrain in place, the screen was full of white lines. The first action I took was to thin the lines used in drawing the terrain from 2px to 1px. This helped. The second was to render all the models in color… When I rendered in color, the increased contrast between the various colors made the 2px lines I had used in the models hard to look at. So I reduced all the lines to 1px. That more or less seemed to work.

Airplanes

Just having tanks as enemies had the potential to get a little boring. I also wanted more ‘things’ in the space above the terrain but below the UI. So I added airplanes. But I had no clear conception of what those airplanes should do. I decided to make them not particularly dangerous if the player was moving, hard to hit, and worth many points relative to the tanks to reward the player for the effort in shooting them down.

Towards the end of development, most of the effort was spent tuning airplanes, the generation rate, collision radius, etc.

Sound and Music

I just wasn’t able to get my sound files small enough to embed, so I generated the music and sound dynamically. Coming into this project I had only the vaguest idea of how to do that. One issue I did have is that the tutorials I looked at for FP10 dynamic sound, all were geared towards people writing sound demos and the like… I couldn’t (at least, on my computer) mathematically calculate on the fly– I needed to pre-calculate them. This is what the game is doing at the very start when it says ‘Setting Up Wavetables’.****

The music was ‘composed’ for this game, but it really has no thematic consistency. Mostly I just wanted to not drive myself crazy hearing the same 4 notes over and over again in the same sequence. After all, I played the game a lot more any player will ever have to!

Like the airplanes I spent a lot of time debugging the sound system. I learned (again) that you can’t just do one thing… As an example, I had coded the outro in a way that it would play even if the game was muted. So I fixed that error. But the state machine was looking for a flag on the outro to sequence through the clean up functions– if the player ended the game muted, now he didn’t hear the outro, but he also couldn’t play again. (That’s fixed now)

Build Size Stats

This may not be of use to anyone, but here is how the game grew over time:

Features Debug Build Release Build
Empty Project 828 bytes
Splash Screen and Preloader 2.55kb 2.01kb
Wireframe Renderer and Movable Camera 5.36kb 4.02kb
Shots and Collision Detection 6.97kb 5.05kb
Explosions 7.66kb 5.49kb
Tank Model and UI 8.49kb 6.16kb
Radar, Exit, Cleanup (BW8) 10.0kb 7.22kb
Shot Pitch, Terrain 11.7kb 8.32kb
Enemies Pitch/Roll, Player Pitch/Roll (AF16) 12.4kb 8.88kb
AF16 with 2 embedded sounds 11.9kb
Dynamic Sound for Shot and Explosion 13.2kb 9.32kb
Synth Module 14.5kb 10.3kb
Synth with Soundtrack 15.1kb 10.4kb
Destructible Terrain (DT16) 15.8kb 10.8kb
Color, Instructions Rotator 16.7kb 11.4kb
Pause, Change view mode (IG16) 17.1kb 11.7kb
1 Line Title Screen 11.8kb
Spin-In Effect 18.4kb 12.5kb
Musical Outro (OS16) 19.1kb 12.9kb
Starfield 20.4kb 13.8kb
Airplane Model and Behavior (AC16) 22.2kb 15.1kb
Cleanup Items (CU16) 15,581 bytes
Final Version :) 15,906 bytes

The final version has 23 classes, with 3,651 lines of code.****** For comparison, the version with a MochiMedia wrapper and leaderboards is 47.7kb and WordPress is telling me this blog post is 2468 words long. :)

Footnotes

*- This however, would have been stupid. It would make more sense to draw the pixels using bitmapData.setPixel(…) or setPixel32(…). :)

**- In fact, I found later in the development, a tutorial by Senocular which suggests just that.

***- Peter Hirchberg attempted it with Vector Tanks on the iPod though (jump to about 45 seconds in):

I bought a copy for ‘Research Purposes’. 😉

****- This may actually be outside the spirit of the competition, because my synth’s wavetables take up about 640kb of ram… on the other hand, the A8 series and contemporary machines had dedicated chips for producing sound, which I can’t assume access to in Flash. On those chips the programmer could generate continuous sound by telling it pitch, amplitude, etc for the desired wave, and the chip would generate the sound independently of the rest of the computer until it was told to do something else. (This is a gross oversimplification).

*****- I’m not sure why wireframe isn’t used more in Flash gaming… although I’m sure there are others the only major games I can think of are Vector Runner and Vector Conflict, from DigYourOwnGrave.

******- Though that’s a little deceptive… the soundtrack is over 300 lines which consist of one note “[3],” and the model classes only define one point per line.

No Comments

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a comment