SSE643.3


 * Course:** SSE643
 * Project:** 3
 * Subject:** Introduction To Game Development
 * Student:** Dmitriy Slipak
 * E-mail:** dslipak@gmail.com

=Table Of Contents=


 * 1) Introduction
 * 2) Using Shaders In The Dark GDK
 * 3) Dark Invaders
 * 4) The Sea Fight Game
 * 5) Conclusion
 * 6) References

 =1. Introduction=

This project is the last in the Advanced Graphic Interfaces course. In this report I will continue covering of shaders techniques used in Dark GDK. I will overview the Dark Invaders game developed by The Game Creators for tutorial purposes. I will also demonstrate progress of the Sea Fight game I developed during this course.

 =2. Using Shaders In The Dark GDK=

A shader is a file that tells Dark GDK how to draw an object. A simple shader may state that every pixel on the object should be drawn bright red, whilst it may not look very useful it does demonstrate that you now have complete control over the color of every pixel used to draw the object.

A shader contains two main parts, a vertex shader and a pixel shader. The vertex shader is responsible for positioning the object and its vertices in the world. The pixel shader then takes these values, along with any textures applied to the object, and paints the object onto the screen in pixels.

The pixel shader part is often the part that is the most complex as, for example, it can also be responsible for applying light to an object. Every pixel that is covered by the object is calculated within the pixel shader applied to that object. When an object does not use a shader the pixel color is calculated by the "fixed function pipeline."

Before shaders became popular, rendering was done using the "fixed function pipeline" which could be described as a shader hard-coded into the graphics card. It contained a fixed set of instructions that lit and textured your objects based on a few parameters passed to it such as light color and textures. However this fixed function could not be changed without changing the hardware and was based upon what was thought everyone wanted their objects to look like. Below is a (very) simplified diagram of the pipeline stages and the data that travels amongst them. Although extremely simplified it is enough to present some important concepts for shader programming. In this subsection the fixed functionality of the pipeline is presented. Note that this pipeline is an abstraction and does not necessarily meet any particular implementation in all its steps.



In here a vertex is a set of attributes such as its location in space, as well as its color, normal, texture coordinates, amongst others. The inputs for this stage are the individual vertices attributes. Some of the operations performed by the fixed functionality at this stage are:


 * Vertex position transformation.
 * Lighting computations per vertex.
 * Generation and transformation of texture coordinates.

Programmable shaders allow to bypass the fixed function pipeline and supply custom functions for how we want our objects to look. What makes shaders useful is their flexibility, ability to color the pixels of an object using any function that will fit inside a pixel shader, from a solid color to a light-mapped reflection shader; and the size of the pixel shader instruction limit is increasing with every new pixel shader version.

There are numerous options available for creating shaders. Since the shader represented by a file, it can be created manually. Below is a simple instructions of brightness for an object from shader file.

code format="cpp" float brightness <   string UIWidget = "slider"; float UIMax = 4.0; float UIMin = 0.5; float UIStep = 0.1; > = 1.500000; code

The Game Creators developed and propose product called Dark Shader for creating customized shaders.



For game development using OpenGL there is free product called OpenGL Shader Designer which will generate a shader files for OpenGL Shader Language.



The Dark Shader will export two files, the DBS file that contains the shader instructions alongside the FX file that also has shader instructions. These files now can be used in a project to apply effects for particular object. Additional effects might be applied via default camera in the project, or via additional cameras. The Dark GDK allows include additional cameras into project. This feature helps create light-rich scenes.

The simplest way to apply shader to a particular object in Dark GDK is via dbLoadEffect function. Here is an example:

code format="cpp" dbMakeObjectSphere(1, 100.0, 250, 140); dbLoadEffect("DetailMapping.dbs", 1, 1); dbSetObjectEffect(1, 1); code

First line above will create sphere object with specified dimension. Next line will load shader instruction into memory. The last line will apply(map) shader instructions to sphere object.

For creation more advanced, rich effects in Dark GDK, shaders might be mixed with cameras via dbLoadCameraEffect function. This combination allows to apply texture and light effects to a particular object at the same time. Here is an example:

code format="cpp" dbMakeObjectSphere(1, 10); dbLoadImage("detail.jpg", 1); dbTextureObject(1, 1); dbMakeCamera(1); dbColorBackdrop(1, 0); dbLoadCameraEffect("Bloom.dbs", 1, 0); dbSetCameraEffect (1, 1, 1); code

Here, first line will create sphere object. Next two lines will apply texture to the sphere object via image file. Next two lines will create camera and change backdrop of it. Next line will load shader instructions into memory. The last line will apply shader instructions to the camera.

These simple examples demonstrate how easy rich effects might be created in the Dark GDK. Unfortunately, I can not demonstrate the output for examples due to reasons explained in previous report.  =3. Dark Invaders=

The Dark Invaders is a full game Dark GDK tutorial. It is a good starting point for a simple 2D game development. The tutorial demonstrates wide specter of Dark GDK abilities as well as some interesting programming techniques. It also shows and explains major game parts, such as game menu, score, game levels, etc. I would not follow entire tutorial in this report. But I will concentrate on some interesting and important aspects.

Apart of many routines in the Dark Invaders code there is at least one part that paid my attention. That is composing and displaying a text on the screen by reading alphabet image file. That is very interesting technique. Below is the alphabet image:



The routine which is responsible for composing text to be displayed on the screen is InvaderText. Here is entire routine:

code format="cpp" void InvaderText ( int iX, int iY , int iSize , char* czText ,                   bool bHorizontalCenter = false ,                    bool bVerticalCenter = false ) {   // if bHorizonalCenter is true then work out the x coordinate // to make the text centered if ( bHorizonalCenter ) {       iX = (int)(( dbScreenWidth / 2 ) -            ( ( strlen( czText ) * iSize ) / 2 )); }

// if bHorizonalCenter is true then work out // the y coordinate to make the text centered if ( bVerticalCenter ) {       iY = ( dbScreenHeight / 2 ) - ( iSize / 2 ); }

// loop through and draw each character for ( int i = 0 ; i < (int)strlen( czText ) ; i++ ) {       if ( czText[i] == 32 ) iX += iSize; else {           dbSprite ( iNextTextSprite, iX , iY , 100 ); // call our routine to change the texture // coordinates of the sprite manually // to show the correct character fixUV( czText[i]); dbSizeSprite ( iNextTextSprite, iSize , iSize ); iX += iSize; iNextTextSprite++; }   } } code

The code is pretty simple. Lets start from function arguments. Here:

//iX// and //iY// - are the position we want to display the text on screen. //iSize// is the size in pixels we want each letter to be (each character is square in our font so its iSize * iSize). //czText// - the text we would like to display. //bHorizontalCenter// – if this is set to true then the text will be centered horizontally. //bVerticalCenter// – same as above but for the vertical center.

First part of the routine is seeing if we need to center horizontally and/or vertically and using new commands for us to work out where the center is. Both dbScreenWidth and dbScreenHeight commands return the width and height respectively of our game screen. If the game is running full windowed at a resolution of 640 x 480 and your desktop is 1920x1200 dbScreenWidth and dbScreenHeight will still return a resolution of 640x480.

The second part of the routine is looping through our string, firstly checking if it is ASCII character 32 which is a space – if it is, we just increase iX ( the variable looking after our current x position) to make the gap. If the character is not a space however we create a new sprite, position it at the current x and y position and assign the font image we loaded earlier to it which is 100.

Remaining lines that I have to explain are:

//fixUV( czText[i])// – is a call to function which will create sprites from provided characters. //dbSizeSprite ( iNextTextSprite, iSize , iSize )// - will make a size for a character sprite created by above function.

The output of this routine in the game presented by the image below:



We can see that InvaderText function created very nice text on the screen.

Below is the Dark Invader game play.



The Dark Invaders game is relatively simple 2D game. Simple in terms of development and play. As I sad earlier it is a very good start for game development. It is very nice tutorial. There is one aspect however, that I have to mention. I took a look at CPU utilization for the game process, and it was pretty high during a play. The CPU utilization was about 50% in average, sometimes jumping to over 70%. Below is a screen shot of task manager window.



This demonstrates requirements to pay attention to code techniques used in the game during development. The same issue did stop me from continuation of the Sea Fight game development and reconsidering my code. I will explain this situation in the next section of the report.  =4. The Sea Fight Game=

The Sea Fight is a simple 2D game I selected to be developed during this course. During the last project I demonstrated several parts of the game developed. There was ships squadron with movement in both directions; fully functional periscope; game informational panel, including information about a ship viewed via periscope at the particular moment. I also introduced model-view-controller paradigm in the game for easier development. The main remaining part for this project was torpedo shooting implementation. However, at some stage of development I noticed that the game's process takes a lot of CPU utilization. The CPU utilization was about 50% in average. This is unacceptable number for a program, and for my game particularly, since not all the parts developed yet. After the code analysis, I started restructuring of it. As a result of this, the game is not completed yet, unfortunately. I will demonstrate some parts of the game developed so far. I will show techniques used. My plan is to release the game during May 2010. The result will be posted in the game wiki page.

I will not follow the entire code as it pretty massive. But I will review some of the important routines.

I added main menu to the game. Once the game started, the main menu screen will be displayed. The main menu screen is presented below.



The main menu part does not contains sprites. It is created via pasting the background image to the screen, and printed out text.

The game code still based on model-view-controller paradigm. But, in the current version there is single controler, single model and single view per game. The mechanism is simple. Controller is responsible only for user-keyboard interaction. Below is set of constants for keyboard handling.

code format="cpp" const enum HOTKEY { KEY_EXIT=18,   //    e    KEY_PLAY=25,    //    p    KEY_STOP=31,    //    s    KEY_CREDITS=46,    //    c    KEY_MENU=50,    //    m    KEY_SHOT=57,    //    space bar KEY_LEFT=203,   //    left key KEY_RIGHT=205   //    right key }; code

Once controller received user request from a keyboard, it will be processed by get_input routine provided below.

code format="cpp" void GameController::get_input(const int& key_code) {   switch(key_code) { case KEY_EXIT: if (((GameModel*)model)->get_action == MENU) ((GameModel*)model)->set_action(EXIT); break; case KEY_PLAY: ((GameModel*)model)->set_action(PLAY); break; case KEY_STOP: ((GameModel*)model)->set_action(STOP); break; case KEY_CREDITS: ((GameModel*)model)->set_action(CREDITS); break; case KEY_MENU: ((GameModel*)model)->set_action(MENU); break; case KEY_SHOT: ((GameModel*)model)->set_action(SHOT); break; case KEY_LEFT: ((GameModel*)model)->set_action(MOVE_LEFT); break; case KEY_RIGHT: ((GameModel*)model)->set_action(MOVE_RIGHT); break; default: ((GameModel*)model)->set_action(NONE); break; }   ((GameModel*)model)->set_key_code(key_code); } code

Here, the controller will update the model with particular game action based on user request.

Model represents a simple game data holder with getters/setters functions and provided below.

code format="cpp" struct GameModel : public Model {   GameModel;

inline int get_key_code { return key_code; } inline GAME_ACTION get_action { return action; } inline int get_game_num { return game_num; } inline int get_periscope_x { return periscope_x; } inline int get_periscope_y { return periscope_y; } inline int get_periscope_east { return periscope_east; } inline int get_periscope_west { return periscope_west; } inline int get_torpedo_x { return torpedo_x; } inline int get_torpedo_y { return torpedo_y; } inline float get_torpedo_scale { return torpedo_scale; } inline int get_target_x { return target_x; }

void set_key_code(const int& value); void set_action(GAME_ACTION value); void set_game_num(const int& value); void set_periscope_x(const int& value); void set_periscope_y(const int& value); void set_periscope_east(const int& value); void set_periscope_west(const int& value); void set_torpedo_x(const int& value); void set_torpedo_y(const int& value); void set_torpedo_scale(const float& value); void set_target_x(const int& value);

bool target_is_set; float reminder;

private: int key_code; GAME_ACTION action; int game_num; int periscope_x; int periscope_y; int periscope_east; int periscope_west; int torpedo_x; int torpedo_y; float torpedo_scale; int target_x; }; code

Once the model received request from controller it will send a request to the view to update the game UI with particular action. Below is a set of game actions.

code format="cpp" const enum GAME_ACTION { NONE=0, INTRO=1, CREDITS=2, MENU=3, PLAY=4, STOP=5, SHOT=6, MOVE_LEFT=7, MOVE_RIGHT=8, EXIT=9 }; code

And below is the routine for game UI update.

code format="cpp" void GameView::update_ui(GAME_ACTION action) {   switch(action) { case MENU: print_menu; break; case PLAY: play; break; case CREDITS: credits; break; case SHOT: move_torpedo; break; case MOVE_LEFT: move_periscope(LEFT); break; case MOVE_RIGHT: move_periscope(RIGHT); break; case NONE: no_action; break; default: break; } } code

Using controller allows to keep main game loop as simple as following:

code format="cpp" void DarkGDK {   GameModel* gm = new GameModel; GameView* gv = new GameView(gm); GameController* gc = new GameController(gm, gv);

while (LoopGDK) { if (gm->get_action == EXIT) break; if (gm->get_action == NONE) gc->get_input(dbScanCode); else gc->get_input(gm->get_key_code); dbSync; }

delete gm; delete gc;   // view will be destroyed in the controller } code

There are many routines in the code created as an experimental routines and probably will be removed in the future. I would mention few of them. The first one is intro routine which represents game story/mission to the user. This routine is the firts executed once user requested to play a game. The code for introduction presented below.

code format="cpp" void Game::intro {   int state = 0; int x = dbScreenWidth/2-180; int y = dbScreenHeight/2-100; std::string text = Util::get_instance-> read_file((char*) Util::get_instance->       get_config("NARRATION").c_str); char chr[1]; bool exit = false;

dbPlayMusic(Util::get_instance->get_object("intro_sound"));

int volume = dbMusicVolume(Util::get_instance->get_object("intro_sound"));

for (int i = 0; i < text.length; i++) { state = dbKeyState(KEY_EXIT); if (state != 0) { exit = true; break; }       strcpy(chr, text.substr(i, 1).c_str);

if (*chr == '|') break; if (*chr == '\\') { x = dbScreenWidth/2-180; y += 12; continue; }

if (*chr == '*') { dbCLS; dbPasteImage(Util::get_instance->get_object("intro_background"), 0, 0); x = dbScreenWidth/2-180; y = dbScreenHeight/2-100; Util::get_instance->delay(10); continue; }

Util::get_instance->print(x, y, chr); x += 7; Util::get_instance->delay(40); }

dbCLS; dbPasteImage(Util::get_instance->get_object("intro_background"), 0, 0);

if (!exit) { if (dbMusicPlaying(Util::get_instance->get_object("intro_sound"))) { while (volume > 0) { dbSetMusicVolume(Util::get_instance->get_object("intro_sound"), --volume); Util::get_instance->delay(50); }       }    }

dbStopMusic(Util::get_instance->get_object("intro_sound")); dbDeleteSprite(Util::get_instance->get_object("intro")); } code

This routine, reads the file with introduction text and prints it character by character with delay in 10 milliseconds. You can watch the introduction below. It has sound too.

media type="youtube" key="re0ZP52ZPIM" width="600" height="400"

Another important routine, which is similar in implementation to intro routine is credits routine for displaying game credits. Here is the code for this routine.

code format="cpp" void Game::credits {   int state = 0; int x = dbScreenWidth/2-170; int y = dbScreenHeight; std::string text = Util::get_instance-> read_file((char*) Util::get_instance->       get_config("CREDITS").c_str);

int i = 0; while ((i = text.find('\\', i)) != -1) text.replace(i, 1, "\n");

dbPlayMusic(Util::get_instance->get_object("intro_sound"));

while (y > dbScreenHeight/2-90) { state = dbKeyState(KEY_EXIT); if (state != 0) { dbStopMusic(Util::get_instance->get_object("intro_sound")); return; }       dbCLS; dbPasteImage(Util::get_instance->get_object("intro_background"), 0, 0); Util::get_instance->print(x, y, (char*) text.c_str); Util::get_instance->delay(1); y--; }

while(state == 0) { state = dbKeyState(KEY_EXIT); if (state != 0) { dbStopMusic(Util::get_instance->get_object("intro_sound")); return; }       dbCLS; dbPasteImage(Util::get_instance->get_object("intro_background"), 0, 0); Util::get_instance->print(x, y, (char*) text.c_str); Util::get_instance->print(dbScreenWidth/2-90, dbScreenHeight-20, "enu"); Util::get_instance->print(dbScreenWidth/2+10, dbScreenHeight-20, "xit"); Util::get_instance->delay(1); } } code

This routine also reads a credits text from file, but displays entire text and moves it from buttom of the screen to the middle.

Here is the example of credits. It has sound too.

media type="youtube" key="HTTPXCzTqjk" width="600" height="400"

The last routine I will demonstrate is torpedo shot. Torpedo in the game should move from the center point of screen bottom. The movement may be direct as well as under different angles from the center point of the screen bottom. Below is the code for routine.

code format="cpp" void GameView::move_torpedo {   if (dbScanCode == KEY_LEFT) move_periscope(LEFT); if (dbScanCode == KEY_RIGHT) move_periscope(RIGHT);

if (((GameModel*)model)->get_torpedo_y >   atoi((char*)Util::get_instance->get_config("HORIZON_Y").c_str)) { ((GameModel*)model)->set_torpedo_scale(((GameModel*)model)->     get_torpedo_scale-0.5); ((GameModel*)model)->set_torpedo_y(((GameModel*)model)->     get_torpedo_y-1);

if (!((GameModel*)model)->target_is_set) { ((GameModel*)model)->set_target_x(((GameModel*)model)->       get_periscope_west+(((GameModel*)model)->get_periscope_east- ((GameModel*)model)->get_periscope_west)/2); ((GameModel*)model)->target_is_set = true; }

float h = dbScreenHeight - atoi((char*)Util::get_instance->     get_config("HORIZON_Y").c_str); float w = 0.0; float s = 0.0;

if (dbScreenWidth/2 > ((GameModel*)model)->get_target_x) w = dbScreenWidth/2 - ((GameModel*)model)->get_target_x; if (dbScreenWidth/2 < ((GameModel*)model)->get_target_x) w = ((GameModel*)model)->get_target_x - dbScreenWidth/2;

s = w/h;

if (((GameModel*)model)->reminder > 1.0) { if (dbScreenWidth/2 > ((GameModel*)model)->get_target_x) ((GameModel*)model)->set_torpedo_x(((GameModel*)model)->         get_torpedo_x-((GameModel*)model)->reminder*s); if (dbScreenWidth/2 < ((GameModel*)model)->get_target_x) ((GameModel*)model)->set_torpedo_x(((GameModel*)model)->         get_torpedo_x+((GameModel*)model)->reminder*(s+0.9));

((GameModel*)model)->reminder = 0.0; }

((GameModel*)model)->reminder += s;

if (fmod(((GameModel*)model)->get_torpedo_scale, 8.5f) == 0) ((GameModel*)model)->set_torpedo_x(((GameModel*)model)->       get_torpedo_x+1);

game->move_object("torpedo",           ((GameModel*)model)->get_torpedo_x,            ((GameModel*)model)->get_torpedo_y,            ((GameModel*)model)->get_torpedo_scale); } else { game->explosion(((GameModel*)model)->get_torpedo_x,     atoi((char*)Util::get_instance->get_config("HORIZON_Y").c_str)); torpedo_ini; ((GameModel*)model)->set_torpedo_scale(100.0); ((GameModel*)model)->set_action(NONE); }

game->print_periscope_coords((((GameModel*)model)->get_periscope_west+ (((GameModel*)model)->get_periscope_east-(((GameModel*)model)-> get_periscope_west+70))/2)-20,       (((GameModel*)model)->get_periscope_west+ (((GameModel*)model)->get_periscope_east-((GameModel*)model)->     get_periscope_west)/2)+20,        ((GameModel*)model)->get_periscope_west,        ((GameModel*)model)->get_periscope_east);

((GameModel*)model)->notify; } code

The code for routine is to massive and requires optimization. You can watch example of torpedo shot as well as explosion animation below. It has sound too.

media type="youtube" key="kKPK0prRZAk" width="600" height="400"  =5. Conclusion=

This course was real challenge for me. I did foud this course as very interesting. The course itself is an excellent start for lerning game design and development. The Dark GDK is a pretty good starting point for game development.  =6. References=


 * 1) The Dark GDK Tutorials (included with the Dark GDK)
 * 2) [|The Game Creators Dark GDK Forum]

Last revision date: {$revisiondate}