Chapter 1

1.1 Introduction

Titled “Dundonia”, this is a 3D platform based adventure game. The user takes control of the main character, Tiny, taken from the DirectX Samples, and guides her through the level towards the finish. She is able to walk and jump. The user must find the skull and avoid all spheres. Tiny has only 3 lives, and a timer is available for personal scores. Two different camera types are available, 1st and 3rd person. A background track is playing which can be toggled on or off.
The special feature that is utilised is the terrain. Not all of the terrain is used in the demo due to its large scale.

The key mappings are tabulated below:

Dundonia Key Mappings

FIGURE 1 – KEY MAPPINGS

1.2 Project Brief

The project brief as provided at the beginning of this project, January 2008:
You are to produce a 3D computer game incorporating the following features:
a) Keyboard and mouse interaction
b) Audio
c) Simple physics (collision detection)
d) Use of an effects file

In addition, your game (or its production) must incorporate knowledge or techniques gained from researching one of following areas:
1) Scripting engines
2) Psychoacoustics
3) Advanced physics (such as rigid body dynamics)
4) Terrain generation
5) Any other “advanced” area that has been agreed with LDN

Chapter 2

2.1 Structure

The application primarily utilises an object-oriented structure. All features are encapsulated within their own classes. This aids readability and allows easy access to all methods giving greater flexibility in designing the feature set of the application. Dundonia’s architecture is based on Frank Luna’s Framework. [ A ]

The overall program consists of 56 files which are made up of 26 header files, 23 main files and 7 effects files. The majority of the files contain only one class but with a few exceptions.

The overall application has been divided into an engine with separate game specific files. Titled the Contagious Engine, it is mainly an integrated collection of the sample files available with [ A ]. It provides the following features:

  • Audio
  • Camera
  • Effects
  • Entities
  • Input Devices
  • Physics
  • Animated Meshes
  • Static Meshes
  • Terrain
  • DirectX Specific Utilities
  • Sky
  • Water

The game specific files consist of the main level itself, the game logic and the splash screens.

2.2 Code Organisation

2.2.1 Contagious Engine:

The purpose and implementation of each feature of the Contagious Engine is described below.

A. Utilities

As Direct3D is a low-level graphics API, a general application framework is required in order to access the available functions. The Utilities section of the engine contains 8 files: d3dApp.h/.cpp, d3dUtil.h/.cpp, GfxStats.h/.cpp and Vertex.h/.cpp.
The d3dApp class is the base Direct3D application class, which provides functions for creating the main application window, running the application message loop, handling window messages, initialising Direct3D, handling lost devices and enabling full-screen mode. Five virtual functions are consistently overwritten in the other files.

  • CheckDeviceCaps
  • onLostDevice
  • onResetDevice
  • updateScene
  • drawScene

The benefit of this structure is that the d3dApp class will handle all of the auxiliary processes mentioned above.

The d3dUtil files contain utility code that many files access. This consists of extra debugging builds, global parameters, extended structures for colour, materials, light and bounding volumes.

The GfxStats class keeps track of and displays the frames rendered per second, milliseconds per frame, and vertex and triangle counts. It also displays all screen messages and the player’s statistics.

The Vertex.h/.cpp files contain vertex structures and declarations that are required by the following classes

  • PSystem
  • UnitCube
  • PlayerCharacter
  • StaticMesh
  • Level1
  • Terrain
  • SkinnedMesh
  • SolarSphere

B. Audio

CAudio.h, CSound.h, SoundGlobals.h, CAudio.cpp and CSound.cpp handle the audio capabilities of the application.
The CAudio class is created to initialise and manage the DirectX audio objects. Creating an object of this class automatically initialises all audio components.
SoundGlobals.h contains the DirectX and audio class headers. The main audio object is declared extern for use in all source code files.
CAudio.h defines the CAudio class. It has a constructor which calls InitAudio() to initialise DirectXAudio. The default directory is set which contains the audio files. The destructor calls KillAudio() which releases all interfaces. Two private objects are declared. The performance object handles the playback related features. The loader object is responsible for loading sound files. SetMasterVolume alters the volume of the application.
The constructor in CAudio.cpp sets the performance and the object loader to Null and then calls InitAudio(). As DXAudio is all COM, it must be initialised first otherwise any other function will fail. To get the performance and loader objects CoCreateInstance() is used. The first parameter is the class identifier, CLSID, of the object to be retrieved. The fourth parameter contains a reference to the identifier and the last one is the address of the variable that receives the object. Then the performance object is used to initialise the performance by calling InitAudio(). Null is passed to the first two parameters, IDirectMusic and IDirectSound objects. A handle to our window is passed in the next parameter. A default audio path is created and 64 channels are allocated to it. DMUS_AUDIOF_ALL enables all audio features and the last parameter means that default values are used for the synthesizer.
CSound.h holds one sound represented by the IDirectMusicSegment8 data type. Member functions manage playback and change the behaviour of the sound file. Pointers to the performance and loader objects are taken from the CAudio class because they are essential for loading and playback of the sound. The constructor is responsible for loading and the destructor unloads the sound from memory.
The segment is created using CoCreateInstance() in CSound.cpp. The filename has to be converted to wide char. The loader object is needed to load a sound from a file. The band data is downloaded to the performance. The destructor unloads the data from the performance so that the segment can be released from memory. The performance is used to play the segment and it is played on the default audio path.

C. Camera

The 1st person camera is implemented in the Camera.h/.cpp files. The 3rd person camera is implemented in the PlayerCharacter class as it follows the character and the relevant matrices are used for other calculations.
Four camera vectors are used to define a local coordinate system for the camera relative to the world coordinate system: right vector, up vector, look vector and position vector. To implement movement in the look vector and right vector, the camera must be translated along those axes respectively. For the up and down movement, the look and up vectors must be rotated around the right vector. For the left and right movement, all the basis vectors are rotated around the world y-axis.

Most of the methods in the main class file are trivial as they are simply for accessing values. The update function updates the camera and builds the relevant view matrices. This is done by calling buildView(). As the mouse input is not required for this demo, the sensitivity of the mouse was greatly decreased. buildView() calculates all the required matrices. The camera’s height is affected by the terrain’s height. The terrain class calculates the correct height of the camera by determining the height of the cell that the camera is currently in. This value then updates the camera.

The Level1 class calls setLens each time the device is reset. This setLens function calculates the aspect ratio of the scene.

For the 3rd person camera, the update function in the Level1 class gets the characters current direction that she is facing. The camera’s lookAt vector is then set to always face the characters position.

D. Direct Input

DirectInput.h defines the class which handles the mouse and keyboard input for the application. This works directly with the input drivers via IDirectInputDevice8. It is used to obtain pointers to the IDirectInputDevice8 objects. Initialising the keyboard and mouse is done by creating the device, setting the data format, setting the cooperative level and acquiring the devices. The devices are polled by using the GetDeviceState method. This returns the current state of the keyboard or mouse in relation to how they have changed since the last time they were checked.

E. Physics

A particle system is utilised in the creation of the fire ring. PSystem.h defines the abstract class which handles the generic work required by the particle system. Any new particle system can be derived from this class. The constructor and destructor primarily do memory allocation work. The constructor automatically allocates enough memory for the maximum number of particles meaning that no extra memory is required at run-time. A time function and world matrices are set. Dead particles are reinitialised when more are needed. The update function increments the time depending on how much has passed between frames. It checks each particle to determine whether or not it is dead and adds it to the appropriate list. The virtual draw() function draws the particle system.

FireRing.h

Random particles are generated on the ring in its local space. A random z-coordinate is generated at some interval for each particle to add depth. The size, initial colour and initial velocity are randomised to add variation to the system. The particle is assigned according to time, mTime. mTime increments and the age of the particle can be determined by computing the difference of mTime – particle.initialTime. The random velocity assigned ensures that the particles do not fly of the ring.

FireRing.fx

The motion of the particles, such as the rotation and the physics calculations, and other adjustments, such as the color fade and size ramp are performed by the vertex shader thus freeing the CPU.

F. Sky

An environment map is used to texture the sky sphere. This sphere surrounds the entire scene with an image of the sky with distant mountains painted on it. The sphere is centred about the camera in world space which gives the illusion that is infinitely far away.

Sky.fx

The sky is assigned the maximum depth buffer values and is never clipped.

G. Water

The water is implemented using normal maps. A triangle mesh grid on the xz-plane represents the water’s surface. By using two normal maps, the normals of high frequency waves can be captured. These normal maps are scrolled over the water surface at different rates and directions. Each pixel is sampled and the average value is rendered. As different rates and directions are used, this gives the illusion of new waves forming and dispersing continuously.

Water.fx

The water reflects the sky’s surface by using an environment map. To simulate ripples and distortion in the reflection, the look up vector of the environment map is perturbed by the net normal. As the net normal varies, this produces the ripple effect.

H. Static Meshes

d3dUtil.h/.cpp contains a function to facilitate loading .X files. The StaticMesh class actually loads the .X files.
The constructor loads the files, calculates the relevant world matrices and bounding boxes. The bounding boxes are calculated by locking the vertex buffer, recording the minimum and maximum vertices of the mesh and then releasing the buffer. These values are then stored in pBBMeshes. pBBMeshes then calls the getWorldAABB() which calculates the minimum and maximum points in each of the axes. The getWorldAABB() then returns the coordinates of the bounding box.
The destructor releases the meshes and textures. The drawScene() function contains the actual rendering code. It applies Phong Shading to the meshes.

PhongDirLtTex.fx

The vertex normals are interpolated across the face of the triangle during rasterization and then the lighting calculations are performed per pixel. Per pixel lighting gives better results than per vertex lighting.

I. Skinned Mesh

The main character, Tiny, contains a hierarchy of bones and a skinned mesh. To animate the character, vertex blending is performed. This animates a particular part of the mesh with respect to the other connected parts.
SkinnedMesh.h defines the class which facilitates the loading and animating of the skinned mesh. The D3DXFRAME structure is used to represent the bones of the character. D3DXMESHCONTAINER specifies what type of mesh it is. The ID3DXAnimationController interface is responsible for animation.
AllocMeshHierarchy.h/cpp contains 4 abstract methods which implement an ID3DXAllocateHierarchy interface. This is required in order to create and destroy a mesh hierarchy using the D3DX functions.
D3DXLoadMeshHierarchyFromX loads the animated character mesh data from the .X file. The bones and vertices which are attached are defined in this file. The hierarchy is searched for the frame that contains the skin data. The vertex format of the mesh is converted using the ConvertToIndexedBlendedMesh method. An array of pointers to the to-root transformation matrices of the bones is constructed.
For drawing the mesh, the position of every bone is recalculated and the transformation matrix is applied to every vertex and its corresponding bones. To show the steps of the animation, the transformation is interpolated between the normal position of the bone and the animated position of the bone, using the time elapsed from the beginning of the animation.

vblend.fx

The shader performs vertex blending with a maximum of two bone influences per vertex. Only one vertex weight is required for this calculation.

J. Entities

SolarSpheres

The spheres provide the obstacles for the character. They are created by animating a mesh hierarchy and the positions are stored in an array in the Level1 class. The outer spheres are children of the centre, and so they rotate about the centre.

UnitCubes

The crates form an indicator of the location of the skull. They are created by constructing unit cubes and are detailed via texture mapping. Once the texture is loaded, it is set as an effect parameter so that it can be used in a pixel shader.

dirLightTex.fx

After the texture coordinates are output for each vertex, they are interpolated during rasterisation. These are then sent to the pixel shader. The diffuse, combined with ambient, and specular properties are passed independently as specular material is not wanted.
PlayerCharacter
This class contains the main character’s code. This class calls the SkinnedMesh class, and the Level1 class calls functions in this class.
The character’s mesh is loaded and the world position and scale is set in the constructor. These are calculated in separate functions. Tiny’s world position, world matrix, yaw, facing, translation, acceleration and jump vectors are calculated. The net directional vector is calculated. The character is then rendered in the drawScene() method.

2.2.2 Game Specific Files:

The game specific files provide the following functionality.

A. SplashScreen and MenuScreen

These classes render the splash-screens and menu respectively. The images are 2D sprites and is accessed from the ID3DXSprite interface.

B. PlayerStats

This file contains extern declared functions for the player’s lives, time and whether or not the player has collected the skull.

C. Level1

CLASS LEVEL1 PROGRAM FLOW_1CLASS LEVEL1 PROGRAM FLOW_2

CLASS LEVEL1 PROGRAM FLOW_3

CLASS LEVEL1 PROGRAM FLOW_4

CLASS LEVEL1 PROGRAM FLOW_5

FIGURE 2 – CLASS LEVEL1 PROGRAM FLOW

Chapter 3

3.1 Terrain Generation

The terrain generated begins with a flat grid. The heights are adjusted at various points and a texture is applied.

3.2 HeightMaps:

A heightmap is created which is used to describe the hills and valleys in the terrain. Using Luna’s heightmap as a base, Terragen was used to modify the map. The format used for this is 8-bit RAW.
A Heightmap class was created to handle the map. This loads the .raw file and filters the image.

3.3 Grid Geometry:

The terrains grid geometry is created by generating the vertices row by row in a top-down fashion. The y-coordinate is modified by setting it to the corresponding heightmap entry. D3DXCreateMesh is used to store the grid which allows the use of the D3DX mesh optimization and utility functions.

3.4 Sub – Grid Culling:

In order to reduce the frame rate by not drawing sections of the terrain, frustum culling was introduced. The terrain was divided into sub grids, and for each sub-grid an AABB was constructed. If no part of the sub-grid’s AABB is visible, then the sub-grid will not be rendered at all.
In order to determine whether an AABB intersects the frustum, six planes of the frustum are required. These planes can be extracted from the projection matrix.

3.5 Textures:

The texture is created by combining several maps to form a net texture. These textures are tiled over the terrain to increase the resolution. A blend map is then used which controls how much of the corresponding texture shows up in the final image.

3.6 Terrain.fx:

This contains the shader used to light the terrain. Specular lighting calculations are removed as the terrain is a mostly diffuse surface.
Fog calculations are also performed.

Chapter 4

4.1 Appraisal

4.1.1 Code Organisation

The code has been separated into many different class files. This is the most optimal approach for large programs. However as the entire program has been put together piece by piece, in comparison to a program that has been designed completely, there are inconsistencies in places. The variable names do not follow a particular convention, specifically between the audio code and the rest of the application. A state manager should have been implemented, providing greater flexibility.

Member variables should not be public, however for a few cases I required access to these parameters. The correct approach would be to create a function that returns these values.

4.1.2 Collision Detection

The collision detection employed in the application is not entirely accurate. This is due to the meshes not being to scale. All meshes were sourced from the internet and the DirectX samples. As the bounding box computes its size according to the size of the original mesh, I applied the same scaling vector on the bounding box. This only worked satisfactorily in some cases.
A more suitable approach is to integrate PhysX. This was my original intention but there were issues with the terrain. I succeeded with the collision detection for the meshes and tiny. As tiny is a skinned mesh, this was not straight forward. This issue was averted by placing a bounding sphere at her feet which provided realistic detections. Regardless of what was tried, importing the terrain into PhysX was fruitless.

4.1.3 Static Meshes

The mesh loading feature of the application could be streamlined. In its current state, each mesh has to be defined individually. Instead of this, the same method could be used for each mesh.

4.1.4 Animation

The main character utilises only one animation. Due to time constraints, it was not possible to research the appropriate functions in order to extract the other animations.

4.1.5 Effects Files

As many calculations are passed to the vertex and pixel shader, this frees the CPU. The .fx files are executed on the graphics card’s GPU. Future designs could endeavour to send more calculations to the pixel shader.

4.2 Evaluation

The mesh fulfils the requirements of the application. Before choosing this type of terrain, I researched the Fractional Brownian Motion terrain provided with the course notes. Whilst it would have been adequate for this project, the current terrain looks more realistic.

The effect file for the terrain also contains fog calculations. It is set quite small but the effect is still noticeable.

4.3 Conclusions

This demo covers a vertical slice of the gameplay of a 3rd person adventure game. It contains a variety of techniques used to render special effects, vertex blending, character animation, terrain rendering, multi-texturing, particle systems, reflections and stencilling using shaders.
A simple game engine has been created which could be adapted to other genres of games. Whilst I did not write the engine from the beginning, I now have an in-depth knowledge of the minimum requirements of an engine, and the issues facing DirectX programmers.
For a novice considering a similar task, I would recommend adding one feature at a time and using a similar implementation method. However if the person is experienced, there is no substitute for perfect design. My next game will be designed completely before any files are created.

4.3.1 Frame Rate

The frame rate is currently running at 38fps. The meshes in the world have consumed too many frames. A more efficient strategy would have been to import all meshes into a 3D modelling package and reduce the polygon count of each one before importing into DirectX.

4.3.2 Debugging

The DirectX Console was used as the program gained in size. Whilst there were no errors in the code, on many occasions’ breaks occurred. The detailed output proved to be a useful tool.

4.3.3 MSDN

I personally found the information available in books, 3rd party websites and forums easier to read than the enormous MSDN. Searching for a specific topic can become quite tedious due to the sheer scale of the forums.

4.3.4 Implementation Method

Being my first game, the majority of the features were new to me. Each new feature was first implemented separately in another project and so if errors occurred which were ambiguous, the original project was used as a guide to rectify the problem.

4.3.5 DirectX SDK Version

The project is not compatible with the March 2008 version without modifications to the audio files. This issue did not really affect me as I had access to the August 2007 version, but I feel that this is a problem that Microsoft has overlooked.

Chapter 5

5.1 References

[ 1 ] Ingmar Rötzler, http://www.two-kings.de/tutorials/dxaudio/
[ 2 ] Keith Ditchburn, http://www.toymaker.info/Games/

5.2 Bibliography

[ A ] Luna, F. D. 2006, Introduction to 3D Game Programming with DirectX 9.0c : A Shader Approach, Wordware Publishing, Texas.
[ B ] Natanson, Dr. L. 2007, Games Programming Course and Laboratory Notes, University of Abertay, Dundee.

VN:F [1.8.8_1072]
Rating: 0.0/10 (0 votes cast)
VN:F [1.8.8_1072]
Rating: 0 (from 0 votes)