COURSEWORK 2: 3D GAME/SIMULATION STUDENT NAME: HARVEY COTTON STUDENT NUMBER: 9612791 LECTURER: PETER PASSMORE Contents Aim 3 Game specificaion 4 Multi-player games 4 Design of objects for Canada track 5 Design of the Canada track 6 Creating a 3d library 7 Creating the game engine 9 Defining a world 9 Adding a player 10 Collision detection 10 Adding weather 12 Casting a shadow 12 Brake lights 13 Creating a background 13 Adding opponents 14 Adding sound 14 Design of a front end 15 Optimising the game 15 Screenshots 16 User guide 17 Summary 18 Further work 18 Acknowledgements 18 Aim The ultimate aim of this project demonstrate a program that addresses the real-time constraints of programming an interactive graphical system. For this project I will be implementing a virtual reality car racing game. I have chosen a car racing game for a number of reasons. Car racing games remain one of the most popular genres to date. Games like Gran Turismo and Ridge Racer for the Playstation have sold millions of copies world wide. The basic elements that make up racing games all contribute to its success or failure; a rich environment, a variety of cars and enjoyable and/or realistic car handling. Everyone can relate to the simple concept of racing. The idea of gaining the best lap time or competing against someone else can be grasped by anyone, regardless of age. The target audience for car racing games is large – catering for all ages and all groups. All racing games vary in one sense or another. Some games may concentrate more on realism and physics whilst others may concentrate more on gameplay. But most car racing games adopt a track based system whereby the player follows a linear track to complete a series of laps. Although this approach is efficient (clipping is easier, etc) it is very limiting. For this project I wish to implement a world based track engine. This means that a world may be easily defined and the player would have complete freedom to drive around it. The advantage of such a system, is that diverse race courses can be created quite easily with no constraints. However, implementation of viewport clipping and AI control is considerably harder. Another major feature of this engine is the ability to select the type of weather the player wishes to race in. Each kind of weather has a different effect on the car handling or visibility. This also means the implementation of graphical effects to simulate the weather. The racing tracks themselves will be set in different locations from around the world (much like other games of a similar genre). Each racing track has its own unique track layout, road surface (which effects car handling) and scenery. The game itself will take place on a flat plane. Commercial racing titles feature terrain with different gradients using various techniques such as look up tables and bezier animation. Such a process requires a lot of time and effort to implement. Typical commercial racing games have development lifecycles varying anything from eight months to nearly two years. I have to make a realistic assessment of what is achievable in this project based on the limited amount of time available. Game specification – Yaroze Rally The theme of the project is to create a world based racing game, where the player has freedom to drive around in the environment. Within the tracks are a six of checkpoints. The player must pass each checkpoint consecutively. Once all checkpoints are passed, it is counted as one lap. The player may select the total laps they wish to race. Two modes will be implemented: Arcade and Time Trail. In arcade mode the player must race against three other computer controlled opponents whereas in time trial, the player can practise on their own. The idea of the game is to complete laps in the shortest possible time. Here is a list of the features I wish to implement: Four racing car each with their own attributes. At least one of three racing tracks – set in Canada, England and Nigeria. Three weather conditions – sunny, rain and fog. Three AI opponents. This game will be designed to run at 30fps at 320x256 resolution. It would not be realistic to implement the project in 60fps with the Yaroze development environment due to the amount of graphical detail in the game and processing requirements. As a result this means the game loses the smoothness of control and animation which is present at 60fps. However, 30fps gives twice as much time per cycle meaning that better graphics and richer environment and effects can be implemented within the engine. Most Playstation racing titles currently on the market run at the same specification. However, newer racing titles have achieved a higher resolution and frame rate due to developments in the professional development system libraries. With more time per cycle, I can introduce more graphical effects into the engine. Graphics can add to the appeal of a game greatly which to some extent justifies the trade off between smoothness of animation for quality and detail of graphics. Here is a list of graphical effects that will be implemented: Billboard graphics – simulating 3d objects using 2d sprites (more efficient). Lens flare effects – using transparent transformed sprites. Dynamic shadow casting – shadows cast by the vehicle implemented using dynamic tmds. Parallax scrolling background – different planes to simulate the distant horizon. Realtime lighting – varies for different tracks and weather conditions. Lighting is applied to all objects and dictates the direction of the dynamic shadow. Multi-player games Most games in a similar genre to this feature multi-player interaction, usually for two players. Extending the interaction to three or four players requires a multitap device. However, the Yaroze environment does not support this. Implementation of basic a system usually involves splitting the screen horizontally – each half corresponding to each player. This technique can be quite tricky because twice the amount of action must be drawn per cycle. Based on the complexity of the game, a two player option may prove unrealistic and better suited for a track based racing game. If such a system were to be implemented, it would feature a split screen two player mode (joypad for each player) where players would complete to finish the laps first. Design of objects for Canada track Here are some sketches of the objects I intend to use in the Canada racing track. All models will be created in 3d Studio Max with the exception of the car model which is taken from the 3d tutorial examples. Once an object is created, it is exported as a dxf file. It is then converted to an rsd file and loaded into Rsd Tool which allows be to paint and texture it. Finally it is converted into a tmd – the Playstations native 3d file format. Design of the Canada track Here is a sketch of the first racing track. Approximate positions of objects within the track are labelled. Care must be taken to ensure that objects are evenly spaced as too many objects in a confined area can put a strain on the Playstation’s Geometry Transfer Engine. Track design is a lengthy process because co-ordinates and orientation must be defined for each object. Creating the 3d library The first step of creating the engine is the design and implementation of a 3d librarycontaining functions and data structures which make development of the game itself easier. In addition to various 3d functions that allow the creation and manipulation of tmd’s, it will include functions and macros for 2d sprites (required for the game’s front end and panels). Here is a summary of the contents of the 3d library: Video operations These are standard operations for performing tasks such as initialising the video mode and functions for management of ordering tables. 2D graphics A complete set of functions that deal with 2d graphics operations such as drawing primitives and sprites. These are required for drawing the interface and panels for the game. 3D Graphics This represents the main bulk of the 3d library. The structure for a 3d object is as followed: typedef struct { GsDOBJ2 handler; // 3d object handler GsCOORDINATE2 coord; // coordinate handler short distance; // Drawing distance char precision; // precision } Object; The structure contains all the necessary information needed to use 3d objects in the game. Once an object is defined, various functions within the library can be used to manipulate the object. E.g. rotating it about an axis or positioning it. Other 3d operations include setting up of light sources and positioning of camera. The camera is an important element in the game. It may be defined as static (positioned fixed within the world) or dynamic (the camera tracks an object that is linked to – used for rear view of car). Billboarding graphics These are custom sprites used for simulating 3d objects. The data structure is fairly similar to that of 3d objects. The structure of a 3d sprite is as followed: typedef struct { GsSPRITE *sprite; // Pointer to sprite VECTOR position; // Position in 3d world long distance; // Maximum draw distance long precision; // 3d precision } Object2; Once the sprites 3d position is defined, it undergoes perspective transformation similar to that of a 3d object. This positions the sprite x,y values and scales the sprite accordingly relative to perspective. The sprite is then inserted into the ordering table like a 3d object based on precision. Maths This section of the library contains various mathematical functions and macros, some of which are used frequently in the game. These include bit operations and bounds checking. An important feature of the engine is the line intersection algorithm. This required to check for collisions that occur in the game (mentioned later on). The function returns a true or false as to whether the collision between two lines is detected. The original algorithm was designed for floating point, which is inefficient for the Yaroze. It is important that the algorithm is modified to used fixed point mathematics (using bit shifting) so that the functions is as fast as possible, because this function may be called frequently by the game. Sound This very short section deals with the loading and playing of sound effects. Pad A collection of macros used for reading the control pad. Creating the game engine Defining a world The first step in creating the game engine, is creating the world which the player interacts with. The simplest way is to define the world as a large array of objects. The way in which the array is defined would be unique to each race course. The data would be stored as followed: x,z position in world y-axis orientation maximum drawing distance (for viewport clipping) type of object This data is entered manually according to the design of the race track. Iterative loops are used to transfer this data into the large array of objects. In addition, data regarding the 3d sprites is needed: x,y position maximum drawing distance type of sprite Positioning the track segments can be a difficult process. To simplify this, a track generation system is used. The data used to create the track is represented as an array of characters. Each character corresponds to the way in which the next piece of the track is placed: ‘1’ means move forward slightly, rotate left 11.25 degrees. ‘2’ means move forward ‘3’ means move forward slightly, rotate right 11.25 degrees. Creating the track is extremely straightforward now. For example, 16 x ‘2’ would produce a 180 degrees bend. Now that the world has been created, the next step is to decide how to draw it. Simply drawing all the objects every cycle is impractical because it would be far too slow. Therefore the objects must be clipped to the viewport. The simplest way to clip objects would be to test which objects lie within a square around the player (figure 1). The test to check whether an object is inside is similar to 2d collision detection and thus it is very fast. However, there is still a large portion of area behind the player which is being drawn. The next idea is to move the clipping square in front of the player (figure 2) by using the position and direction of the player. As a result, there is less wastage and more can be drawn. Adding a player The next step of developing the game engine, is to add the player to the world. The position of the player dictates what is to be drawn in the world. The player car model is defined and placed in the world. Once the player has been placed in the environment, the player wants to be able to move the car around using the control pad. There are only four actions the player should be concerned with: Accelerating - pressing CROSS button Braking/reversing - pressing SQUARE button Steering left - pressing LEFT button Steering right - pressing RIGHT button The way in which the car moves when the buttons are pressed, will vary depending on which of the four cars the player has chosen. This means that constants need to be defined which give the car its own characteristics: typedef struct { long speed; // Fixed point maximum speed long acceleration; // Fixed point acceleration long steering; // How quickly the car turns long brake; // Braking constant (varies according to track/weather) long impact; // Coefficient that determines speed reduction after impact } Constant; The focus of this project is on car racing as a game rather than simulation, therefore the physics can kept simple. Speed, acceleration, steering and braking are intuitive concepts and enable the player to differentiate between the different cars. The impact (mentioned later on) is concerned with the effect a collision has on the speed of the car. These constants do not alter whilst the player is in the game, only when the player picks a car. A dynamic camera is linked to the player, meaning that the viewport follows the car whenever it goes. The camera itself is placed behind the car to give a third person perspective. However, the camera should pan left and right (similar to commercial games) so that the player is given the effect that the car is turning. This technique is simple. The camera is given a target position (set by the steering) and the camera slowly moves towards that position. This gives the player the added illusion that the car is turning. Collision detection Basic collision detection for this game would be to performing bounding box test for some of the objects in the environment. This would be inadequate for this engine for two reasons; it would consume a large amount of processing time and the objects in the game are not perfect boxes. A solution to both these problems is to use the line intersection function defined in the 3d library. Instead of testing individual objects, an easier method would be to define boundaries on the track as x,z lines. In order to check these against the player, the boundaries of the player must be calculated (figure 3). Two lines are defined for the player – the left side (L1 & L2) and the right side (R1 & R2). These points can be calculated very easily by using the same technique used to find the direction the player is facing in. Checking the car against the boundaries can now be performed. This checking can be made even faster by performing a bounding box test between the boundary against the player. If the test is true, then the line intersection can be tested. So now the player may drive around the environment and collision with boundaries can be detected accurately. The next step is to decide the effect the collision – this proves more difficult than testing the collision. In order to make the player react to the collision, information must be gathered first: The direction the player is facing. The speed the player is travelling at. The direction the boundary is facing. The constant determining the reduction in speed after impact. Multiplying the player direction with the player speed and border direction gives us the resultant shift in the players position. Once the player is shifted, their speed is reduced. The final data structure for a boundary is as followed: typedef struct // Collision boundaries { long x1,y1,x2,y2; // Position of line on XZ plane long x,y; // Bounding box position short w,h; // Bounding box width & height VECTOR dir; // The direction the border is facing } Border; Border border [64]; The next type of collision detection to think about is the player collision with the checkpoints. This simply involves find the position of each checkpoint and performing s bounding box test against the players position. This is perfectly adequate because accurate collision checking is not required. The checkpoint which is tested depends on the next checkpoint the player is required to pass. Each time the collision is detected, a counter for the checkpoint and lap is updated accordingly. Special effects – adding weather The player may now drive around the race track. Collision prevents the player passing through the barriers and checkpoints are tested. With the basis of the engine in place, new features can be added. The first feature to implement is the ability to select different weather conditions. Here is a list of the different weather conditions and the effect they have on the engine and the player: Sunny Driving conditions are default. Sun and lens flare effect is created – this is done by calculating the position of the sun according to the angle of the player, and then positioning the lens flare based on the position of the sun on the screen. The sun is drawn in the far distance, but the lens flare effect is drawn close up. Lighting is set to maximum – in the direction of the sun. Large shadow is cast by player. Rain Braking distance is doubled. Rainfall effect is created – this consists of a star field system using three layers of raindrops falling at different velocities to give the effect of 3d movement. The rain moves left or right if the player turns. Brake light reflection effects used to simulate wet ground (see brake lights). Lighting is poor. Shadow cast by player is small. Fog Player visibility is reduced. Using the 3d fog lighting may prove impractical – because it ignores the billboard graphics and the background. Another idea to simulate fog would be to draw a layer of different shaded transparent boxes over the scene. Lighting is moderate. Shadow cast by player is small. Casting a shadow Using the direction of the light and the directional vectors for the player (which have already been calculated, a shadow cast by the player’s car can be created. This is not a true shadow, but it creates the effect. Before generating the shadow, the direction of the player must be found (figure 4). Once the segment that the player is facing is found, a dynamic tmd can be created using the dynamic tmd header file. The shadow is generated using two quadrangles. There are four variations of the shadow – one of which is selected depending on the direction the player is facing. Using the boundaries of the car and the direction of the light, the shadow can be created (figure 5). Brake lights When the player brakes, the brake light on the car should light up. There are two ways of approaching this – create two models for each car (one with brake lights, the other without) or to create 3d sprite brake lights (transparent red spheres). The latter is easier in this case because creating two models of each car consumes more memory and the effect may not look so good. Drawing the brake lights is involves creating two 3d sprites and placing them behind the car (using L2 and R2 already calculated). The perspective is calculated on its own so the position is set automatically. Reflection of the headlights on the road can be created using the brake light positions. Like the shadow, this is not a true reflection but it looks similar. For reflected brake lights x position on the screen is copied and duplicate break lights are drawn at the bottom of the screen. These duplicates are scaled and made transparent. Creating a background The background is used to create the effect of a distant horizon. It needs to be able to turn when the player turns. This is done by creating a wrappable sprite and positioning it according to the angle at which the player is facing. A second layer is drawn behind this horizon – the sky which moves more slowly than the horizon. This creates a parallax effect. Combinations of horizon and sky are used depending on the track or weather. Adding opponents Artificial intelligence (AI) represents one of the most challenging aspects of racing games. There are many different ways of creating computer controlled players, most of which are very time consuming to produce. AI in track based games is easy – the opponent may be knocked around or collide, but it always knows which direction it is going in. With a world based racing games, things are a lot more difficult. Two approaches can be used: Use a series of waypoints which the opponent moves towards. Use different sampled data of car movements on the track. Due to time constraints, I have chosen to use sampled data. It is easier, requires little processing and gives the opponent realistic movement. However, unlike using the waypoints it means that the opponents movement throughout the track never changes. It also means collision detection between the player and opponent is pointless since the opponent cannot react to the collision. For this particular game, using sampled data does achieve the effect of AI adequately and the player can race against these opponents like real AI players. Three samples of data are required for each of the three opponents. Each of the sets of sampled data represent an easy, medium and hard difficulty to add some variety to the game. The data consists of three elements; x position, y position and y-angle which represent the opponents position each cycle. In order to record this data, the following steps are taken: Make minor alterations to the code to facilitate logging (using printf to console). Upload game to Yaroze. Press F5 to begin logging of console. Play the track (data is recorded). Quit the game. Once quit is selected, the program dumps the data to the console using printf. Exit siocons. Data is stored in log file which is then arranged and placed in AI.H header file. Using the data stored in the header, opponents can be drawn into the environment and their position and orientation is fed from the array – which animates the opponents. The next step is to calculate the player’s position in the race relative to the opponents. Once again, this proves difficult because it is world based (there is no linear path). One method of checking is to use the waypoints around the track. The player’s position is calculated by finding out how many checkpoints have been passed and how much time has elapsed between them. This technique means that although the players position can only be calculated each time they pass a checkpoint, it does give an accurate measurement of the player’s progress. Adding sound Implementing sound into the game is a relatively straightforward process. Once the wave files are processed into the files sound.vb and sound.vh, the relevant sound is played depending on the action such as sound of a collision. The engine sound is created by altering the key of the engine sound. The faster the vehicle the travels, the higher the key at which the sound is played. Design of a front end The front end will consist of a simple menu driven interface which is controlled using the UP, DOWN and CROSS buttons. As a whole, the front end will be comprised consists of several element. The flowchart below (figure 6) shows the entire layout of the game structure: Optimizing the game The final step once the game has been created is to optimise the game engine to ensure it runs as fast as possible (allowing everything to fit into 1/30th of a second). If a cycle exceeds that (equivalent to 624 hsyncs) then the game experiences slow down. The main technique employed by my engine, was to rewrite the code responsible for drawing the track. The rendering of the track represents the main overhead in processing. In order to reduce the time spent rendering the background, level of detail (LOD) is used. It is generally the most effective way of drawing huge amounts of detail without slow down. The idea is to draw objects at different levels of detail depending on the distance from the camera. This is done by calculating the distance of the object from the camera and adjusting the object's subdivision based on that distance. This results in a huge reduction in processing requirement. Another technique that is used is dcaching. The PSX has an area of memory 0x1f800000-0x1f800400 which is 1K in size. This area can be accessed by the CPU 5-6 times faster than normal memory. As with Register, you can only use this with local variables. The idea is to take your data structure or array and allocate space in dcache for it. E.g. BULLET bullet; bullet.x=100; becomes register BULLET *bullet=(BULLET *)getScratchAddr(0); bullet->x=100; This technique makes a huge difference when used in frequently called functions. Using this technique, I can rewrite sections of the 3d library (such as the draw_model function). Screenshots of the finished game Title screen Car select First track in rain User guide Playing the game Race through checkpoints 1 to 6 in the fastest possible time in the track, weather and car of your choice. Your next checkpoint is indicated in the top left hand corner (CHECK) as well as the current lap (LAP). There are two modes of play to choose from: ARCADE MODE Race against 3 other opponents and compete for 1st place. TIME TRIAL Practise the track on your own. Options LAP Set the laps required to finish the race. VOLUME Adjust sound volume using LEFT and RIGHT buttons. ADJUST SCREEN Position the screen according to your preferences. Controls D-PAD Car steering CROSS Accelerate SQUARE Brake/reverse START+SELECT Exit current game Summary I was able to implement everything within the original design specification. Although I expected to create at least one of the racing tracks, I would have liked to have implemented all three. However, track design proved to be the most lengthy part of the implementation. The game itself runs as it should. There are minor glitches in the graphics, but this is dues to the numerous levels of precision used in the game. Glitching in shadow and some of the scenery could be fixed by creating two ordering tables – one for the enivronment and one for the player and AI cars. There are also minor problems with the camera. Due to the problem with fixed point, whenever the position of the player is near or above 30000 it causes the rotation to jitter. The use of camera panning does reduce this problem significantly, but it can be fixed by keeping the player positioned at 0,0 and rotating/moving the world around the player. This would require some extensive modifications to the engine. Further work Many different features can be implemented into the game to bring it closer to a commercial quality title. Here are some other ideas for expanding the game further (in addition to more tracks or better AI): Using a varying gradient surface – e.g. hills or slopes. Multiplayer options – split screen 1 on 1 racing. Another idea is a tag game whereby players drive around the track and tag each other. Improved physics – implementation of power sliding (skidding round corners), choice between manual and automatic gears. Action replay – this would record the players movement and replay it to the user using different camera angles. Game saves – saving best times to memory card. Acknowledgements Thanks to James Russell for help with developing billboarding techniques. Dynamic tmd header from Robert Swan race 2 demo (originally adapted from code copyrighted by Sony). The car model used is a modified version of the one used in the 3d tutorial examples. Sprite trees and sound effects were acquired from various sites on the internet. PAGE 1 PAGE 18