You are on page 1of 7

Andrew Cottrell

EEE2041

6170976

Computer Vision and Graphics Coursework Report 3D Tank Game.


Andrew Cottrell 6170976

Introduction
The assignment was to create a game using OpenGL, GLu and GLut. It was to read in a map given to us in the specification, to create a tank to move around the environment created and collect tokens. It was to include accurate world physics in the game. It should use textures and lighting to simulate a realistic world with a camera following the turret of the tank.

Method
File layout and makefile
This assignment was by far the biggest program I have written, so keeping it neat and tidy for ease of read and debug was essential. I chose to split up my program into multiple .c files with accompanying .h files. This allowed me to keep all the functions in the correct file, so for example if I was having a problem with the tank rendering, I would go to tank.c. The header file would contain prototypes of all the functions as well as hash defines so they could all be used in the other files. Struct definitions were also in the header files guarded by if defined guards. The files are separated into the following files: bmp.c, camera.c, game.c, hud.c, input.c, main.c, map.c, mesh.cpp (provided), physics,c and tank.c, each with their header file. When compiling a large number of files, using a makefile is a must. The makefile is of a standard orientation. It compiles using g++ as the provided mesh code was in C++ while the rest of the code written by myself was in C, luckily C compiles under C++ by definition. To make simply type make in the working directory, or to clean up the files made during compilation type make clean. The makefile compiles and links the application for me, with the arguments Wextra and Wall on the compilation stage for debug messages, and lm lglut lGLU -lGL to include all of the necessary libraries on the linking stage. I also used the GIT revision control system for the project to allow me to see my changes and revert if necessary. This came in helpful multiple times while accidentally creating a bug and to see what I had changed to fix the problem. I kept all of the maps in the directory ./maps/ for easy location or the user to edit or add additional maps. All of the textures other than the tank one are kept in ./textures/. There is also a README.md in the directory which contains information on how to make the program, bugs, explanation on how it works and what libraries are needed for compilation.

Map information
Reading in the map from a file was the first task, as how it was laid out into the file was not specified, I chose to use comma separators between values, and new lines indicating the next level on the axis. I also 1|Page

Andrew Cottrell

EEE2041

6170976 # map one, in the pdf, # 0 = empty, 1 = cube, 2 = token on cube 1,1,1,0,0,0,0,0,0,0 0,0,1,0,0,0,2,2,2,2 0,0,2,0,0,0,1,0,0,1 1,0,1,0,0,0,1,0,0,1 1,0,1,0,0,0,1,1,2,1 1,0,2,1,1,1,1,0,0,0 1,0,0,0,0,0,1,0,0,0 2,2,2,2,2,1,1,0,0,0

included the ability for comments to be added to map files with the # symbol meaning that anything after that until a newline would be ignored by the program. An example map file is shown on the right. I reversed the order used in the specification to allow the tank to always spawn at 0, 0. Making generation of extra maps easy and keeping the maths clean. I generated a few functions to read in the map given a string containing the address, to print it out to stderr for debug, and some conversion functions to put the data into a useable state.

The map data is stored in a global array with the limits set in map.h. The map is read in through nested for loops which then are input validated and the program will exit on failure. A global containing the top score possible on that map is also generated during this.

Camera setup
The camera data is stored in a struct to allow for the addition of multiple camera angles later on in the development stage, in my program only one is used. There are two primary functions, reshape and camera. Reshape sets up the projection matrix which is the information about the camera, including its field of view and how far it can see into the distance. These values never change unless the window is resized. Camera is fed a pointer to the struct which camera is being used at the current time, and then used that to set up the model view matrix with the gluLookAt function inputting the camera coordinates and where it is to be looking at. The up direction is always set to be the positive Y axis. Altering these values during the start of the assignment allowed me to check if the map was being rendered, until later in the program they were managed dynamically with reference to the location of the tank.

Generating the map + specials


Drawing the map from the already recorded array involved using nested for loops to cycle through the map coordinates and drawing a cube wherever a 1 or 2 was encountered. Initially I used glutwirecube to draw the map, however as it came to texturing the cubes I discovered that this did not return the normal coordinates, so I had to generate my own function to draw a cube and feedback the normal coordinates. Again in a nested for loops I do the same procedure, only drawing teapots instead of cubes when the array reaches a 2 instead of 1. I also used two for loops to generate a mesh of lines on the base of my map to show a base.

Input
Next was getting some user entry to allow for the camera to move around and see how the map was laid out. Initially I used glutkeyboardfunc to read in keypresses, and to move the camera location by a set value each time. This worked however was clunky, you could only press one key at once and would not be smooth at all. I changed to a route that used glutkeyboardupfunc and glutkeyboardfunc to alter the states of a 256 array to indicate the keyboard state. This meant on each key down it would alter it to show the key is down, and the same on up. I then used a function to be called in display to check the current array state and do the required functions on any key. This made the movement a lot smoother.

2|Page

Andrew Cottrell

EEE2041

6170976

As these keys would be registered as fast as the computer could run the program, a single key press could be read multiple times. To stop this on buttons which required debouncing I put their functions into the keydown function, which allowed for only one press per keystroke. I implemented reading wasd for movement, q and e for strafing, p for pausing, f for a frames per second monitor and esc for quitting the program. Later I made strafing only possible in debug mode (godmode) to allow for moving around the map, as it did not fit with the realistic movement of the tank. Next I implemented mouse movement, so that I could look around the scene freely with the mouse, and eventually the turret. To do this I used the function glutpassivemotion, as well as glutmotion which meant it would be ran no matter of the state of the mouse buttons. These functions are fed the x and y coordinates of the mouse, so to use this I had to convert these arbitrary numbers into a useful figure. I did this by initially making the middle of the map 0,0 instead of the previous top left corner. I then made this number turn into being an angle to made rotation easier. After this there is a glut function which moves the mouse cursor back to the middle of the screen, this meant that you could never move the mouse off the screen unless paused meaning you could rotate in one direction for as long as you could move the mouse. The implementation of the pause key lead to disabling the cursor and reenabling it, meaning that you could not see the mouse when playing the game giving it a more realistic feel. Pause is a state set in a game state struct which then is checked on multiple functions so that if it is pressed the functions become unusable. At this point I also initialised the ability to do things on mouse clicks, while I had nothing to do on these other than print that it had been clicked. If a mouse button that was not left middle or right it would quit the program as it did not understand what is being fed to it, ie mousewheel.

Loading the tank


Loading the tank was primarily done by the code that was given to us in mesh.cpp. Firstly I loaded in the entire tank file, but later this was replaced with each of the four components so that each could be individually moved. I briefly edited the mesh.cpp file to return a value if it could not find the file location given to the object as this was a problem I encountered earlier on. Each mesh is globally declared, and then a function on initiation reads them into the variable. Each mesh is then drawn at the correct location found in its struct containing all the data about that tank. This was also kept in a struct to 1) allow for multiple tanks to be added later. 2) keep it out of being a global variable. I then set up a camera function to set the camera variables in the struct to be dependent on the tank variables, thus making the camera follow the tank. I also made the angle read in from the input function alter the direction of the turret, and have the camera pointing there.

Physics
Physics was one of the major parts of this game, as introducing it changed how the movement worked a lot. Firstly I worked on getting the tank to accelerate and decelerate, meaning that if you tapped w, it wouldnt instantly stop as before, but instead would keep rolling at a sane rate, and the same with s for reversing. This all worked out relatively easily and worked nicely. 3|Page

Andrew Cottrell

EEE2041

6170976

Turning presented more of a challenge, as I could not figure out how to easily implement proper tank turning. I stumbled upon a guide which gave a nice basic outline on how to make car like turning for a game and started to implement this. It went along the lines of find the position of the front and back wheels, then find out how far they would each travel if they were going in the direction they were pointing, and then average the tank to be around the two spots. After a bit of fiddling this implementation seemed to work very well and turning was looking very realistic. For all of this I implemented a macro which would calculate the time between the previous call of the function and the new call of the function in milliseconds, which would help assist in all the maths of this. Before implementing this; speed and turn rate of the tank was FPS dependant, but implementing this meant that no matter how fast the game ran, the time would be dependent on real world time and thus constant. There were a multitude of SUVAT equations implemented in this part of the code to get the correct and smooth tank movement.

Collision detection
Collision detection is done by taking in the tank coordinates, correcting it for a little offset and converting it to an integer. Then this is put into the array and the value is read back. If it is a 0 then the game state dying is set and the tank falls in the y axis until it hits the floor, and then respawns. If it is a 1 then nothing is done. If it is a 2 then the variable in the map array is altered to read a 1, while also adding one onto the score and checking if that was the last token on the map, and doing what is appropriate if it was. If the user loses all of their lives, then the map is reinitialised which makes sure all of the previous specials are re put into the array allowing for them to be collected again.

Textures
Loading in textures was one of the more difficult tasks, firstly I had to create a function to load in .bmp images into a mode which could be used by opengl. With the help of a stack overflow post, I managed to get this working and properly reading in a file, while hard to test in standalone, the next stage of putting the textures onto the tank model was relatively easy. The textures are all read into a struct containing the data, and two information headers for each bmp file. These functions are sent the location of the texture, as well as the location of the struct to set the data into. Using opengl commands I bound the texture to the tank, and I could see the bmp had been correctly loaded in. However it wasnt being displayed properly so I had to enable backface culling on the tank for it to display properly. As mentioned before, texturing the cubes was not as easy as hoped as the standard function did not return normal. I therefore wrote a few functions quickly to generate a cube with the normals being returned so I could load a texture on top of these too. Another texture was added instead of the grid on the base, I raised this level a bit too, which allowed for the tank to fall into the water before respawning, instead of dying the instant it touched it. I did this by just generating a square and placing the texture on top of it, instead of the previous grid of a large quantity of lines. 4|Page

Andrew Cottrell

EEE2041

6170976

Lighting and Depth


Enabling lighting was relatively easy, once I had fully understood it and had debugged the problems it had caused on other parts of the program. Both lighting, depth and texture need to be enabled and disabled around appropriate parts for the image to render properly. For example the HUD should not have any of these applied to it as it is an orthogonal display. My game uses two lights, one which creates a general ambient light so that everything is still visible to the human eye, and no faces are blacked out, secondly a diffused light which allows for lighting to be viewable on each side of the objects. These are both enabled in the appropriate places and disabled accordingly. Depth was a simple enable just had to be disabled appropriately as this was causing problems as well.

Heads up Display
The Heads up Display (HUD) was implemented by firstly altering the camera layout to use an orthogonal display. This meant that it had a 2D field on top of the 3D environment. The HUD displays a multitude of information for the user to read and interpret. The primary HUD function checks multiple game states before printing each piece of data. The data printed optionally were FPS, paused, dying and game over. The other pieces of information were constantly displayed no matter on game state; crosshair, level, lives, total score, score, time and weapon state. The weapon state alters what is printed, and its own colour depending on the weapon state. When the weapon is charged it states CHARGED in red writing and elsewise prints the time until the weapon will be charged in white coloured font. The crosshair shows the middle of the screen, and where the camera and turret are pointing at. The crosshair is drawn with two very simple lines crossing the screen.

Shooting
Shooting was a difficult task to figure out, I decide that my tank should fire a laser due to it looking cool, and there also being time constraints left on the project at this point. Setting up the weapon to fire was simple as I had already got mouse clicks read properly in, I first got it to print the location that the crosshair was pointing at, I also then got it to print the location of that direction at a set range away, to see if it was correctly working. Next I set up a for loop which would count up the distance that it had travelled, and at each step check the location of the laser/projectile as it went through there. This detection worked in the same way as the tank location so that if the weapon went within a cube at all the teapot (special token) would be hit, as there was no checking if it had gone through the actual teapot. I wrote another function to draw a line from the firing point to the end point for a second after each shot, and then it would disappear after this. I noticed that this weapon was very powerful and made the game far too easy, so I decided to give it a cool down for recharging to increase the difficulty and not encourage the user to stand still and shoot the entire map to win the game.

5|Page

Andrew Cottrell

EEE2041

6170976

Results

Note: Cursor is hidden in game, however comes up on screenshots.

6|Page

Andrew Cottrell

EEE2041

6170976

Video of gameplay can be found at: https://www.youtube.com/watch?v=K_tyHmkiYVU

Missing features and bugs


Sadly time ran out on the project before I could finish everything that I wished I couldve done. These include: Making the wheels spin as the tank moves Correcting the bug of the wheel rotation when steering Creating a leader board for lowest times to complete the game Adding more levels where you have to shoot to reach some tokens Adding title menus and menus in between levels A game complete screen Adding sound Altering the lighting and fog depending on the levels.

Conclusion
In conclusion, Ive produced a well-rounded game that is smooth to play and meets all of the key criteria. The movement and turning of the tank, as well as moving of the camera with the mouse respond perfectly and the game overall looks good while still being efficient to run well on low end systems. While it is lacking in a few points, for example the shooting is not perfect, they are still playable and the game is rather fun. If I had a bit of extra time I wouldve liked to neaten up my code a bit more, and improve efficiency as well as add a few extra levels with a multitude of themes. However my code is definitely sufficiently neat enough for be worked upon and is not one big file which is impossible for someone who has not written it to understand. I have clearly understood the elements of openGL, including lighting and camera angles as displayed in the game, and adding extra elements to the game is more of a time consumer than extreme challenge. Items like adding enemy AI would have been a challenge to implement, which I wouldve liked to have done. I have learnt a great deal from this coursework, as this is the biggest programming project I have ever done. Ive learned how to write a makefile, how to properly use GIT, as well as the entirety of openGL, how to read in BMP files and how to generally write a game. Ive also learned a lot about structs and passing them between functions and multiple files, including learning how to if guard in header files. I now feel far more confident in my ability to actually program anything longer than a simple script as done in previous assignments.

7|Page

You might also like