Alpha Shooter - OpenGL and implementation features

Various features of OpenGL were used in this project, ranging from basic modelling and projection to slightly more advanced ones. No extensions to the standard or external libraries (except one for importing TGA and OBJ files) nor pre-made game engines have been used so far.

After data structures are initialized some GLUT callbacks are set up, notably calling glutVisibilityFunc() and glutIdleFunc() to manage frame animation. The idle function, that updates movement and calls glutPostRedisplay(), is cancelled temporarily when the window isn't visible (it would be useless to continue drawing it) and when the game is paused. The game window may be resized at will or choosing among three default sizes (640x480, 800x600, 1024x768); it's also possible to play in full screen.

The player moves around the evironment via the keyboard or the mouse (see below). To manage input, GLUT callbacks are set with glutKeyboardFunc(), glutSpecialFunc(), glutMouseFunc(), glutPassiveMotionFunc(), while to deal with key repetition and be able to use more keys at once the functions glutSetKeyRepeat(), glutKeyboardUpFunc() and glutSpecialUpFunc() are necessary. Depending on the events received the game memorizes modifications to operate to the camera before the next frame is drawn; in version 0.0.1 it would build rotation and translation matrices that, when properly combined, would produce a 4x4 matrix containing all the required information about camera position and orientation. However, I found this scheme not to suit my purpose anymore, as the game evolved, so quaternions are currently used instead, with an orientation quaternion kept in memory and updated every time a movement is processed. The orientation is then converted to its matrix form to extract the vectors needed by gluLookAt().

All the operations between vectors and matrices are operated through a library written from scratch, partly inspired to others that are found on the Web (mostly in C++ when it comes down to computer games). The rotation and translation coefficients of both the camera and moving objects are properly scaled according to the number of frames per second that the game is able to draw at run-time, in a way to try to maintain, in most cases, similar performance on most machines independently from hardware.

The camera moves parallel to the floor and rotates around the axes of the system of reference of the scene. The player may activate a "flight mode" (it is imagined the played character carries a jetpack, which will become an item you can pick up in a future release), in that case it is also possible to move along the up vector and thus fly up and down. A very simple collision detection scheme is employed to add a minimum of realism to the game: the data structures commonly used for this purpose haven't been introduced in the game yet (octrees, quadtrees etc., to be implemented), instead a trivial control on the position of the camera (and the rockets that are shot, see below) prevents the player from going through the walls, ceiling, floor and the fence in the south-east zone.

Back to top

The environment is lit by three kinds of lighting: lights coming from the ceiling, a positional spotlight that follows the camera and is always pointing forward, and a weak environmental illumination. The six lamps on the ceiling are drawn as simple surfaces with different materials, changing GL_EMISSION values according to their state. The spotlight that follows the camera, and the character, is positioned before the view transform operates at the beginning of every iteration of the display callback, so that it doesn't stay fixed in the scene. The default parameters for it are set during initialization, including cone width, attenuation, color. All lights can be activated and deactivated at will, except the environmental illumination which is always active.

Back to top

Floor and walls are completely covered with tiled textures, different ones where appropriate. All the textures used are in TGA format, in RGB or RGBA mode, loaded and transformed into the internal format used by OpenGL. For every texture mipmaps are built automatically using the function gluBuild2DMipmaps(), for a better graphical effect.

All textures used are bidimensional and are used to modulate the color of the materials assigned to the various objects and of the lights that hit the objects. The fence all around the south-eastern area is drawn using a texture that makes use of the alpha channel of the image, so that it has transparent parts, and with depth test deactivated, in order to be able to see what's behind the fence itself.

Texture coordinates for quadric surfaces like barrels and the planet are generated automatically; in the case of the planet the size of the surface makes the texture appear stretched and blurry, but this works just as well as the surface of a planet seen from above doesn't necessarily have to be very detailed. For vertexes of polygonal meshes that are specified "by hand" in the code, texture coordinates are also specified with glTexCoord2f().

Back to top

By activating the control console near the center of the room it's possible to start a mini-game of skill in which the largest number possible of targets out of the twenty that appear must be shot. The targets, red wireframe spheres that remind of holographic projections, move along a random path at random speeds; the player must anticipate their movement and shoot them with rockets. As each target gets hit it turns green and slowly disappears, diminishing its alpha value progressively.

Both the targets and the rockets move along a path that is calculated at the "birth" of each object: at every frame the position of the object along the path is updated so that it gets closer to the final point. In the case of the targets a cubic Beziér curve is generated by taking four control points at random in 3D space, in order to obtain a non-predictable course, while in the case of rockets the path is a segment (curve of degree one) that always starts close to the camera and ends on a point along the Z axis from the starting point, at a predefined distance (the rockets can obviously hit something before they reach their detonation point and end their course sooner). In order to find where the object should be at every frame the De Casteljau algorithm is used, with a suitable value for the variable parameter between 0 and 1. If the corresponding option is activated in the menu (see below), trajectories for the targets are drawn (together with the control points) using one of the evaluators provided by OpenGL. It is also possible to activate the drawing of projection rays from the holographic projector that ideally generates the targets. With these additions it is much easier to know both where a target is and where it is going to be.

A target and its trajectory

Targets and visualization of trajectories

Everytime a rocket hits a target, a wall or other object, or ends its course without hitting anything, it explodes. The explosion is implemented via a simple system of particles that are generated in real time and expand from the point of impact radially outward, with a random speed; particles dissolve similarly to the targets. In addition to these a set of solid debris is released during the explosion, drawn as triangles that rotate randomly around their baricenter and spread slowly outward from the center of the explosion. The rockets also leave a trail of particles generated similarly to those released during the explosion, representing the propulsion system for the rockets.

An explosion of particles

Explosion, particles and debris

Rockets are not modelled like the other surfaces with calls to the primitives of OpenGL, for them a different technique was used instead, modelling a mesh with Blender and assigning suitable materials, exporting the model afterwards in Wavefront OBJ format to be imported in OpenGL with Nate Robins's C library.

Back to top

Most of what gets drawn, especially everything that doesn't change between frames because it's static, like floor, ceiling, and walls, is stored during initialization in a series of display lists. These are called as needed in the display() callback so that it's not necessary to re-calculate in real time modifications in the model matrix, vertex and texture coordinates, normals and more, and gain in performance.

Back to top