sse643.2


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

=Table Of Content=

Preface Game Terrain Shaders Demo/DemoScene The SeaFight Game(Continued) Util Module SeaFight Driver Module Game Module BattleShip Module Scene Module Periscope Module Info Panel Module Model-View-Controller Dark GDK Surprises References

 =Preface=

This report is a continuation of my research on the Advanced Graphical Interfaces topics. It contains brief overview of game terrains and shaders. I will also briefly explain other topics in Advanced Graphical Interfaces, such as demoscenes. Second part of the report contains explanations of the SeaFight game development started in project one.

 =Game Terrain=

Action games, especially 3D games usually contain one of the most important graphical elements – terrain. The common way for the game terrain creation is generation the terrain via texture and bump mapping.

Texture mapping is a method for adding detail, surface texture (a bitmap or raster image), or color to a computer-generated graphic or 3D model. A texture map is applied (mapped) to the surface of a shape or polygon. This process is similar to applying patterned paper to a plain white box.

Multitexturing is the use of more than one texture at a time on a polygon. For instance, a light map texture may be used to light a surface as an alternative to recalculating that lighting every time the surface is rendered.

Bump mapping is a computer graphics technique to make a rendered surface look more realistic by modeling the interaction of a bumpy surface texture with lights in the environment. Bump mapping does this by changing the brightness of the pixels on the surface in response to a heightmap that is specified for each surface. When rendering a 3D scene, the brightness and colour of the pixels are determined by the interaction of a 3D model with lights in the scene. After it is determined that an object is visible, trigonometry is used to calculate the "geometric" surface normal of the object, defined as a vector at each pixel position on the object. The geometric surface normal then defines how strongly the object interacts with light coming from a given direction using Phong shading or a similar lighting algorithm. Light traveling perpendicular to a surface interacts more strongly than light that is more parallel to the surface. After the initial geometry calculations, a coloured texture is often applied to the model to make the object appear more realistic.

The Dark GDK allows create terrain for the game in a few simple steps. Major aspects during terrain development with Dark GDK are height map and texture data, and order of instructions(functions) call. Below is terrain example based on Dark GDK tutorial.

A height map is used as a way of representing a terrain in a 2D image. This is then imported into game world and transformed into a 3D object. Here is a height map file map_1.bmp, I created with GIMP.

Texture data is an image file which will represent surface texture in the game. Here is sample texture image slimey.bmp used in example.

Another image file, sandstone_T.bmp added to simulate surface details.

For sky representation, I have used sky05.x model from Dark GDK files library.

Next is an order of instructions(functions) calls during terrain development. Here is set of functions from Dark GDK and order in which they should be called. code format="C++" void dbSetupTerrain(void); void dbMakeObjectTerrain(int iID); void dbSetTerrainHeightMap(int iID, char* szFile); void dbSetTerrainScale(int iID, float fX, float fY, float fZ); void dbSetTerrainLight(int iID, float fX, float fY, float fZ, float fRed, float fGreen, float fBlue, float fScale); void dbSetTerrainTexture(int iID, int iDiffuse, int iDetail); void dbBuildTerrain(int iID); code Here is complete source code for terrain example. code format="C++"
 * 1) include "DarkGDK.h"

void DarkGDK(void) {   dbSyncOn; dbSyncRate(60); dbSetDir(".\\resources");

dbSetCameraRange(1.0, 30000.0);

dbLoadImage("slimey.bmp", 1); dbLoadImage("sandstone_T.bmp", 2);

dbSetupTerrain;

dbMakeObjectTerrain(1);

dbSetTerrainHeightMap(1, "map_1.bmp"); dbSetTerrainScale(1, 3.0, 0.6, 3.0); dbSetTerrainLight(1, 1.0, -0.25, 0.0, 1.0, 1.0, 0.78, 0.5); dbSetTerrainTexture(1, 1, 2);

dbBuildTerrain(1);

dbLoadObject("sky05.x", 2);

dbSetObjectLight(2, 0);

dbScaleObject(2, 30000, 30000, 30000); dbPositionCamera(385, 23, 100);

dbSetObjectTexture(2, 3, 1);

while (LoopGDK) { dbControlCameraUsingArrowKeys(0, 2.0, 2.0);

float fHeight = dbGetTerrainGroundHeight(1,           dbCameraPositionX, dbCameraPositionZ);

dbPositionCamera(dbCameraPositionX,           fHeight + 10.0, dbCameraPositionZ);

dbUpdateTerrain; dbSync; } } code Project files can be found at the course ftp server under my name. Once compiled, the program output will be as follows.

As we can see, with Dark GDK we can build very nice terrain just in few simple steps.  =Shaders=

Before introduction to shaders I have to explain why I can not represent output result for the shader example below. I started this course on my MacBook Pro which has ATI Radeon X1600 chipset with Windows 7 installed over BootCamp utility. This is pretty powerful combination for game play and development. At least this is what I thought in the beginning of this course. Unfortunately, during couple of months experiments with Dark GDK and OpenGL the ATI Radeon X1600 on MacBook Pro got affected. The result is small yellow squares over the screen during some of applications run. For instance, I can see those unusual elements when Safari web browser displays “Top Sites” page. Another example is PhotoBooth application. As a result I will continue this course on my second laptop – IBM (not Lenovo) Thinkpad T42 which has ATI Mobility Radeon 7500 chipset. This is pretty poor chipset at present time but it will allow me finish this course. Unfortunately, ATI Mobility Radeon 7500 chipset does not supports any of Shader Model, and shader example below produces result that is not expected.

Moreover, I can not use Windows 7 since Lenovo still did not release driver for ATI Mobility Radeon 7500 under Windows 7. AMD also does not supports this chipset after IBM-Lenovo deal. Using default driver from Microsoft cases DirectX throw multiple unexpected exceptions. So, my current architecture is IBM Thinkpad T42 with Windows XP and original driver from AMD for ATI Mobility Radeon 7500. Anyway, shaders seams to be very interesting and important topic.

Now, lets see what is a shader in general. Here is definition from wikipedia. In the field of computer graphics, a shader is a set of software instructions, which is used primarily to calculate rendering effects on graphics hardware with a high degree of flexibility. Shaders are used to program the graphics processing unit (GPU) programmable rendering pipeline, which has mostly superseded the fixed-function pipeline that allowed only common geometry transformation and pixel shading functions; with shaders, customized effects can be used.

The Game Creators, who originated the Dark GDK simplified this definition. A shader is a file that tells Dark GDK how to draw an object. For example 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.

More specifically 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, without which commands such as dbPositionObject and dbRotateObject would have no effect, and produces a small set of values which are passed to the pixel shader. 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 hardcoded 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. However, programmable shaders allow you to bypass the fixed function pipeline and supply your own functions for how you want your objects to look. What makes shaders useful is their flexibility, you can color the pixels of an object using any function that will fit inside a pixel shader, from a solid color to a lightmapped reflection shader; and the size of the pixel shader instruction limit is increasing with every new pixel shader version.

In the future the fixed function pipeline will no longer exist, as is the case with DirectX 10, since shaders are now far more useful than the fixed function pipeline was, the hardware space once used for the fixed function can now be used to help increase the speed and size of user defined shaders. Also a shader can be written that could replicate any feature the fixed function pipeline currently has. So a shader takes control of the positioning, lighting and texturing of an object that would normally be handled by fixed processes and lets you specify your own method of texturing and lighting you want applied to your objects.

OpenGL also allows extended usage of shaders via OpenGL Shading Language. GLSL, as the OpenGL Shading Language is commonly called, is a programming language for creating programmable shaders. Introduced as a part of OpenGL version 2.0, the OpenGL Shading Language enables applications to explicitly specify the operations used in processing vertices and fragments. Additionally, GLSL helps unlock the computation power of modern hardware implementations of OpenGL.

Working with shaders using Dark GDK is very simple. The major part is creating a shader. We can use DarkShader application from The Game Creators to create a shader, or create a shader manually. I created a shader manually since DarkShader is a commercial application. I took a shader provided with Dark GDK and applied modifications in Notepad. Here is a fragment from modified shader. code format="C++" float detailScale <   string UIWidget = "slider"; float UIMax = 16.0; float UIMin = 0.1; float UIStep = 0.01; > = 4.000000;

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

texture BaseTex : DIFFUSE <   string ResourceName = "door2.jpg"; string ResourceType = "2D"; >; code Complete source code for shader example using dark GDK presented below. Project files can be found at the course ftp server under my name. code format="C++"
 * 1) include "DarkGDK.h"

void DarkGDK(void) {   dbSyncOn; dbSyncRate(60); dbSetDir(".\\resources");

dbMakeObjectCube(1, 1); dbMakeObjectSphere(1, 100.0, 250, 140); dbLoadEffect("DetailMapping.dbs", 1, 1); dbSetObjectEffect(1, 1);

dbSetCameraToObjectOrientation(1, 1);

while (LoopGDK) { dbTurnObjectLeft(1, 0.5); dbSync; } } code Unfortunately, the output of this program on my laptop is just white rotating cube. Shader is applied but is not visible. The reason is explained above.  =Demo/DemoScene=

Being talking about advanced graphical interfaces I can not avoid such interesting topic as demo or demoscene. The demoscene is a computer art subculture that specializes in producing demos, which are non-interactive audio-visual presentations that run in real-time on a computer. The main goal of a demo is to show off programming, artistic, and musical skills. There are multiple categories of demoscenes divided by platform and size. The key-words in demoscene are real-time and size. Production file for the demoscene must be 1K, 4K, 64K, etc. Imagine a program with real-time heavy graphics generation and rendering being 1K or 4K of size. Here is an example of winner demoscene produced by RGBA people

media type="youtube" key="_YWMGuh15nE" width="400" height="300"

The program(production) above has following parameters: 4096 bytes executable, realtime rendering, directx, deferred texturing, procedural content creation, sound synthesis, data compression, noise derivatives, assembler, motion blur.  =The SeaFight Game(Continued)=

Since project one the SeaFight game changed dramatically. First of all the code was completely restructured and organized more logically. I prefer keep separated declaration(header files) and implementation files. This way source files become less in size and more readable. Functionality of the game expanded and improved. Several design patterns has been applied, particularly singleton pattern and model-view-controller pattern. If you did not take SSE 658 course, please consider to take it since it's very helpful and important for software engineers. Let me demonstrate in details what was done with the SeaFight game during this project.

The game now is driven by configuration file (seafight.conf) located in "resources" directory which contains set of the game constants. Here is a fragment of the seafight.conf file. code format="C++" RESOURCES_DIR=.\\resources\\ WINDOW_TITLE=SeaFight WINDOW_WIDTH=640 WINDOW_HEIGHT=512 SYNC_RATE=60 SCENE_IMG=seascape.jpg PERISCOPE_IMG=periscope.png PERISCOPE_SPEED=2.0 INFO_PANEL_IMG=info_panel.png BATLESHIP_SND=battleship.wav BATTLESHIP_WIDTH=60 BATTLESHIP_HEIGHT=25 TORPEDO_SND=torpedo.wav code

The game at current stage has a battleship squadron(fully functional), periscope(fully functional), information panel(functionality incomplete), added squadron sound. The battleships squadron now contains ten ships created as separate sprites.

Current game structure contains following modules:


 * Util Module** – Utilities module, contains utilities functions such as string formatting, datatypes casting, I/O manipulations, common to any part of the game functionality.


 * Seafight Driver Module** – The game entry point module.


 * Game Module** – Module which contains the game major functionality.


 * Battleship Module** - Module which contains battleship functionality.


 * Scene Module** – Module for the game scene manipulation.


 * Periscope Module** – Module for periscope functionality.


 * Info Panel Module** – Module for the game information functionality.


 * MVC** – The Model-View-Controller design pattern implementation.

I will do brief overview of each module below.  =Util Module=

Files: util.h, util.cpp

One of the reasons why I created utilities module was dictated by Dark GDK object creation mechanism. Every object created using Dark GDK has an numeric ID argument. Dark GDK keep reference for every created object in the game via object ID which is numeric(integer) datatype. I did find difficult remember of objects IDs during the game development.

Utill module contains ObjectMap class which keep track of objects created in the game. code format="C++" struct ObjectMap {   std::string key; OBJECT_TYPE type; int id; }; code

The ObjectMap class contains object's numeric ID as well as key (string value) associated with particular object and type of the object.

Util module also contains Utilities class with set of common game functions. code format="C++" class Utilities { private: static Utilities* instance; int object_id; std::ifstream* in; std::vector* registry; char* temp_buffer;

protected: Utilities;

public: ~Utilities;

static Utilities* get_instance; void create_object(std::string key, char* source_file,       OBJECT_TYPE type, int ix, int iy); int get_object(std::string key); std::string get_object(int id); std::string get_config(std::string key); void set_config(std::string key, std::string value); void clean_registry; void print(std::string text); void print(int x, int y, char* text); char* get_str(const int& value);   //    dbStr replacement void print_fps(int x, int y); }; code

Utilities class implemented using Singleton design pattern. Object based on Utilities class created once in the game and accessed via get_instance function.

One of the main functions in the Utilities class is create_object function. It creates an object in the game based on type provided and stores object information (ID, key, type) in the ObjectMap container. ID of the object created internally, so we don't have to remember last created object ID to increment it. To get reference to particular object in ObjectMap container we can using get_object function. We can provide object ID or object key(string value) to get particular object.

Functions get_config and set_config responsible for reading/writing the game configuration file.

The get_str function is replacement for dbStr function from Dark GDK. Original dbStr function causes memory leaks, and I decided to replace it with get_str function.

The print function has two overrides and used for printing out the game information to the game information panel.

Below is complete util module declaration and implementation.

Util module declaration: code format="C++"
 * 1) ifndef _UTILITIES_H_
 * 2) define _UTILITIES_H_


 * 1) include
 * 2) include
 * 3) include
 * 4) include
 * 5) include "darkgdk.h"


 * 1) define CONFIG_FILE "resources//seafight.conf"

const enum OBJECT_TYPE { SPRITE, SOUND, CAMERA, LIGHT }; const enum DIRECTION { RIGHT, LEFT };

struct ObjectMap {   std::string key; OBJECT_TYPE type; int id; };

class Utilities { private: static Utilities* instance; int object_id; std::ifstream* in; std::vector* registry; char* temp_buffer;

protected: Utilities;

public: ~Utilities;

static Utilities* get_instance; void create_object(std::string key, char* source_file,       OBJECT_TYPE type, int ix, int iy); int get_object(std::string key); std::string get_object(int id); std::string get_config(std::string key); void set_config(std::string key, std::string value); void clean_registry; void print(std::string text); void print(int x, int y, char* text); char* get_str(const int& value);   //    dbStr replacement void print_fps(int x, int y); };

code
 * 1) endif

Util module implementation: code format="C++"
 * 1) include "util.h"

Utilities* Utilities::instance = 0;

Utilities* Utilities::get_instance {   if (instance == 0) instance = new Utilities;

return instance; }

Utilities::Utilities {   object_id = 0; registry = new std::vector; in = new std::ifstream(CONFIG_FILE); temp_buffer = new char[sizeof(int)*8+1]; }

Utilities::~Utilities {   if (in->is_open) in->close; clean_registry; delete registry; delete in; }

void Utilities::create_object(std::string key, char* source_file, OBJECT_TYPE type, int ix, int iy) {   if (get_object(key) != 0) return;

ObjectMap object = { key, type, ++object_id }; registry->push_back(object);

switch (type) { case SPRITE: dbLoadImage(source_file, object_id, 1); dbSprite(object_id, ix, iy, object_id); break; case SOUND: dbLoadSound(source_file, object_id); break; case CAMERA: dbMakeCamera(object_id); break; case LIGHT: dbMakeLight(object_id); break; } }

int Utilities::get_object(std::string key) {   int id = 0;

for (std::vector::iterator it = registry->begin; it != registry->end; it++) { if (it->key == key) { id = it->id; break; }   }

return id; }

std::string Utilities::get_object(int id) {   std::string key;

for (std::vector::iterator it = registry->begin; it != registry->end; it++) { if (it->id == id) { key = it->key; break; }   }

return key; }

std::string Utilities::get_config(std::string key) {   std::string value;

if (in->is_open) { std::string line; size_t split_pos; std::string tmp;

while (!in->eof) { getline(*in, line); split_pos = line.find('='); tmp = line.substr(0, split_pos);

if (strcmp(tmp.c_str, key.c_str) == 0) { value = line.substr(split_pos+1);

//   remove carriage return character char* c_str = (char*) value.c_str;

for (int i = 0; i < value.length; i++) { if (c_str[i] == '\r') c_str[i] = '\0'; }

value = c_str;

break; }       }

in->seekg(0); }

return value; }

void Utilities::set_config(std::string key, std::string value) {   std::ofstream out("tmp.conf");

if (in->is_open) { std::string line; size_t split_pos; std::string tmp;

while (!in->eof) { getline(*in, line); split_pos = line.find('='); tmp = line.substr(0, split_pos);

if (strcmp(tmp.c_str, key.c_str) == 0) line = tmp + '=' + value;

out.write(line.c_str, line.length); out.write("\r\n", 2); }

out.close; remove(CONFIG_FILE); rename("tmp.conf", CONFIG_FILE); } }

void Utilities::clean_registry {   std::vector::iterator it;

for (it = registry->begin; it != registry->end; it++) { switch (((ObjectMap)*it).type) { case SPRITE: dbDeleteImage(((ObjectMap)*it).id); dbDeleteSprite(((ObjectMap)*it).id); break; case SOUND: dbStopSound(((ObjectMap)*it).id); dbDeleteSound(((ObjectMap)*it).id); break; }   }

registry->clear; }

void Utilities::print(std::string text) {   dbPrint((char*)text.c_str); dbSync; dbSync; }

void Utilities::print(int x, int y, char* text) {   dbText(x, y, text); }

//   dbStr replacement due to memory leaks caused by dbStr char* Utilities::get_str(const int& value) {   return itoa(value, temp_buffer, 10); }

void Utilities::print_fps(int x, int y) { char* fps;

fps = new char[10]; strcpy (fps, "fps = "); strcat(fps, get_str(dbScreenFPS)); print(x, y, fps);

delete [] fps; } code

 =SeaFight Driver Module=

Files: seafight_driver.cpp

This is the game's entry point. Its short and straight forward. code format="C++"
 * 1) include "game.h"

void DarkGDK(void) {   Game* game = new Game; game->play; delete game; } code

 =Game Module=

Files: game.h, game.cpp

The game module contains main game functionality. At this stage it contains functions for battleships squadron creation and manipulation as well as main game loop.

Main functions are:

play function – contains the main game loop; setup_squadron – function to create battleships squadron; move_squadron – function for battleships move; rotate_squadron – function for squadron rotation

Game module declaration: code format="C++"
 * 1) ifndef _GAME_H_
 * 2) define _GAME_H_


 * 1) include
 * 2) include "scene.h"
 * 3) include "battleship.h"
 * 4) include "info_panel.h"

class Game { private: Scene* scene; std::list* squadron; DIRECTION squadron_dir;

void initialize; void setup_squadron(const int& size); void move_squadron; void rotate_squadron; void reset_squadron;

public: Game; ~Game;

void play; };

code
 * 1) endif

Game module implementation: code format="C++"
 * 1) include "util.h"
 * 2) include "game.h"

Game::Game {   initialize; }

Game::~Game {   delete scene; delete squadron; delete Utilities::get_instance; }

void Game::initialize {   dbSetDir((char*) Utilities::get_instance->get_config("RESOURCES_DIR").c_str); dbSyncOn; dbSyncRate(std::atoi((char*) Utilities::get_instance->get_config("SYNC_RATE").c_str)); dbSetWindowTitle((char*) Utilities::get_instance->get_config("WINDOW_TITLE").c_str);

dbSetWindowSize(std::atoi((char*) Utilities::get_instance->get_config("WINDOW_WIDTH").c_str),       std::atoi((char*) Utilities::get_instance->get_config("WINDOW_HEIGHT").c_str)); dbSetImageColorKey(255, 255, 255);   //    transparent

dbInk(dbRGB(160, 2, 0), dbRGB(255, 255, 255));   //    red color Utilities::get_instance->print("Initializing...");

//   load game sounds Utilities::get_instance->create_object("battleship_sound",       (char*) Utilities::get_instance->get_config("BATLESHIP_SND").c_str, SOUND, 0, 0); Utilities::get_instance->create_object("torpedo_sound",       (char*) Utilities::get_instance->get_config("TORPEDO_SND").c_str, SOUND, 0, 0);

//   create and load game scene scene = new Scene;

// create and setup squadron std::string dir = Utilities::get_instance->get_config("SQUADRON_DIRECTION"); if (dir == "LEFT") squadron_dir = LEFT; if (dir == "RIGHT") squadron_dir = RIGHT; setup_squadron(std::atoi((char*) Utilities::get_instance->get_config("SQUADRON_SIZE").c_str));

dbDrawSpritesFirst; }

void Game::setup_squadron(const int& size) {   squadron = new std::list; std::string name; std::string img; std::string info_img; float x = 0.0;

for (int i = 0; i < size; i++) { name = Utilities::get_instance->get_config("BSHIP_" +           (std::string) Utilities::get_instance->get_str(i+1) + "_NAME"); img = Utilities::get_instance->get_config("BSHIP_" +           (std::string) Utilities::get_instance->get_str(i+1) + "_IMG"); info_img = Utilities::get_instance->get_config("BSHIP_" +           (std::string) Utilities::get_instance->get_str(i+1) + "_INFO_IMG");

if (squadron_dir == RIGHT) if (i == 0) x = -atof((char*) Utilities::get_instance->get_config("BATTLESHIP_WIDTH").c_str); else x = -(atof((char*) Utilities::get_instance->get_config("SQUADRON_INTERVAL").c_str)*i)- atof((char*) Utilities::get_instance->get_config("BATTLESHIP_WIDTH").c_str); if (squadron_dir == LEFT) if (i == 0) x = std::atof((char*) Utilities::get_instance->get_config("WINDOW_WIDTH").c_str)+ atof((char*) Utilities::get_instance->get_config("BATTLESHIP_WIDTH").c_str); else x = std::atof((char*) Utilities::get_instance->get_config("WINDOW_WIDTH").c_str)+ atof((char*) Utilities::get_instance->get_config("BATTLESHIP_WIDTH").c_str) + atof((char*) Utilities::get_instance->get_config("SQUADRON_INTERVAL").c_str)*i;

squadron->push_back(new BattleShip(name, img, info_img, x,           atof((char*) Utilities::get_instance-> get_config("SQUADRON_Y").c_str), squadron_dir)); } }

void Game::move_squadron {   std::list::iterator iter;

for(iter = squadron->begin; iter != squadron->end; ++iter) { if (((BattleShip*)*iter) == squadron->back) { if (squadron_dir == RIGHT) { if (((BattleShip*)*iter)->x >= std::atof((char*) Utilities::get_instance->get_config("WINDOW_WIDTH").c_str)) reset_squadron; }           if (squadron_dir == LEFT) { if (((BattleShip*)*iter)->x <= -atof((char*) Utilities::get_instance->get_config("BATTLESHIP_WIDTH").c_str)) reset_squadron; }       }

((BattleShip*)*iter)->move; } }

void Game::rotate_squadron {   if (squadron_dir == RIGHT) squadron_dir = LEFT; else squadron_dir = RIGHT;

squadron->reverse;

std::list::iterator iter;

for(iter = squadron->begin; iter != squadron->end; ++iter) ((BattleShip*)*iter)->rotate(180.0, true); }

void Game::reset_squadron {   int i = 0; std::list::iterator iter;

for(iter = squadron->begin; iter != squadron->end; ++iter) { if (squadron_dir == RIGHT) { if (i == 0) ((BattleShip*)*iter)->x = -atof((char*) Utilities::get_instance->get_config("BATTLESHIP_WIDTH").c_str); else ((BattleShip*)*iter)->x = -(atof((char*) Utilities::get_instance->get_config("SQUADRON_INTERVAL").c_str)*i)- atof((char*) Utilities::get_instance->get_config("BATTLESHIP_WIDTH").c_str); } else { if (i == 0) ((BattleShip*)*iter)->x = std::atof((char*) Utilities::get_instance->get_config("WINDOW_WIDTH").c_str)+ atof((char*) Utilities::get_instance->get_config("BATTLESHIP_WIDTH").c_str); else ((BattleShip*)*iter)->x = std::atof((char*) Utilities::get_instance->get_config("WINDOW_WIDTH").c_str)+ atof((char*) Utilities::get_instance->get_config("BATTLESHIP_WIDTH").c_str) + atof((char*) Utilities::get_instance->get_config("SQUADRON_INTERVAL").c_str)*i; }       i++; } }

void Game::play {   PeriscopeModel* p_model = new PeriscopeModel; PeriscopeController* p_controller = new PeriscopeController(p_model); InfoPanelController* ip_controller = new InfoPanelController(p_model);

std::list::iterator iter;

for(iter = squadron->begin; iter != squadron->end; ++iter) ip_controller->subscribe((BattleShip*)*iter);

dbPlaySound(Utilities::get_instance->get_object("battleship_sound")); dbLoopSound(Utilities::get_instance->get_object("battleship_sound"));

while (LoopGDK) { if (dbEscapeKey) break;

//   squadron rotation //   for demonstration purposes only //   will be replaced in project 3 if (dbSpaceKey) { dbSleep(100); rotate_squadron; }

p_controller->run; move_squadron; dbSync; }

dbStopSound(Utilities::get_instance->get_object("battleship_sound"));

delete ip_controller; delete p_controller; delete p_model; } code

 =Battleship Module=

Files: battleship.h, battleship.cpp

The battleship module is responsible for battleship data storage and manipulation. Main functions are:

move – function for battleship move functionality; rotate – function for battleship rotation

The BattleShip class inherited from Model class and is a part of MVC pattern which I will explain later.

Battleship module declaration: code format="C++"
 * 1) ifndef _BATTLESHIP_H_
 * 2) define _BATTLESHIP_H_


 * 1) include
 * 2) include "util.h"
 * 3) include "mvc.h"

struct BattleShip : public Model {   int id; std::string name; std::string image; std::string info_image; int image_w; int image_h; float x;   float y;    float speed; DIRECTION direction;

BattleShip(std::string n, std::string i, std::string info_i,       float x_pos, float y_pos, DIRECTION dir);

void initialize; void move; void rotate(float angle, bool change_dir); };

code
 * 1) endif

Battleship module implementation: code format="C++"
 * 1) include "battleship.h"

BattleShip::BattleShip(std::string n, std::string i, std::string info_i,                      float x_pos, float y_pos, DIRECTION dir) {   name = n;    image = i;    info_image = info_i; image_w = atoi((char*) Utilities::get_instance->get_config("BATTLESHIP_WIDTH").c_str); image_h = atoi((char*) Utilities::get_instance->get_config("BATTLESHIP_HEIGHT").c_str); speed = atof((char*) Utilities::get_instance->get_config("SQUADRON_SPEED").c_str); x = x_pos; y = y_pos; direction = dir; initialize; model_type = "ship"; }

void BattleShip::initialize {   Utilities::get_instance->create_object(name, (char*) image.c_str, SPRITE, x, y); id = Utilities::get_instance->get_object(name); dbSizeSprite(id, image_w, image_h); //   load info image dbLoadImage((char*) info_image.c_str, id+1000);

if (direction == LEFT) rotate(180.0, false); }

void BattleShip::move {   if (direction == RIGHT) x += atof((char*) Utilities::get_instance->get_config("SQUADRON_SPEED").c_str); else x -= atof((char*) Utilities::get_instance->get_config("SQUADRON_SPEED").c_str); dbSprite(id, x, y, id); notify; }

void BattleShip::rotate(float angle, bool change_dir) {   float curr_angle = dbSpriteAngle(Utilities::get_instance->get_object(name)); dbFlipSprite(Utilities::get_instance->get_object(name));

if (direction == RIGHT) { if (change_dir) direction = LEFT; curr_angle -= angle; dbOffsetSprite(Utilities::get_instance->get_object(name), image_w, image_h-1); } else { if (change_dir) direction = RIGHT; curr_angle += angle; dbOffsetSprite(Utilities::get_instance->get_object(name), 0, 0); }

if (!change_dir) dbOffsetSprite(Utilities::get_instance->get_object(name), image_w, image_h-1);

dbRotateSprite(Utilities::get_instance->get_object(name), curr_angle); notify; } code

 =Scene Module=

Files: scene.h, scene.cpp

The scene module is a simple module responsible for the game scene creation and manipulation. It is small and straight forward.

Scene module declaration: code format="C++"
 * 1) ifndef _SCENE_H_
 * 2) define _SCENE_H_

class Scene { private: void initialize;

public: Scene; };

code
 * 1) endif

Scene module implementation: code format="C++"
 * 1) include "scene.h"
 * 2) include "util.h"

Scene::Scene {   initialize; }

void Scene::initialize {   Utilities::get_instance->create_object("scene",        (char*) Utilities::get_instance->get_config("SCENE_IMG").c_str,        SPRITE, 0, 0); dbSizeSprite(Utilities::get_instance->get_object("scene"),       std::atoi((char*) Utilities::get_instance->get_config("WINDOW_WIDTH").c_str),        std::atoi((char*) Utilities::get_instance->get_config("WINDOW_HEIGHT").c_str)); } code

 =Periscope Module=

Files: periscope.h, periscope.cpp

The periscope module is responsible for the periscope functionality in the game. The periscope module implementation based on Model-View-Controller(MVC) design pattern. Here is where the SSE 658 course comes in to play. The periscope module contains PeriscopeModel class inherited from Model class and PeriscopeController class inherited from Controller class. This allows easy periscope data updates based on the periscope movements. The same approach applied for the game's informational panel and will be explained later.

Periscope module declaration: code format="C++"
 * 1) ifndef _PERISCOPE_H_
 * 2) define _PERISCOPE_H_


 * 1) include "util.h"
 * 2) include "mvc.h"

struct PeriscopeModel : public Model {   float x;    float y;    float l_point; float r_point;

PeriscopeModel {       x = y = l_point = r_point = 0.0; model_type = "periscope"; }

inline float get_x { return x; } inline float get_y { return y; } inline float get_r_point { return r_point; } inline float get_l_point { return l_point; }

void set_x(const float& value); void set_y(const float& value); void set_r_point(const float& value); void set_l_point(const float& value); };

class Periscope;

class PeriscopeController : public Controller { public: PeriscopeController(Model* m);

void update(Subject* s); void move(DIRECTION d); void run; };

class Periscope : public View { public: void initialize; void print_coordinates; void move; };

code
 * 1) endif

Periscope module implementation: code format="C++"
 * 1) include "periscope.h"

/// ///   PeriscopeModel implementation /// void PeriscopeModel::set_x(const float& value) { x = value; notify; } void PeriscopeModel::set_y(const float& value) { y = value; notify; } void PeriscopeModel::set_r_point(const float& value) { r_point = value; notify; } void PeriscopeModel::set_l_point(const float& value) { l_point = value; notify; }

/// ///   PeriscopeController implementation /// PeriscopeController::PeriscopeController(Model* m) : Controller(m) {   ((Periscope*)view)->initialize; }

void PeriscopeController::move(DIRECTION d) { if (d == RIGHT) { if (((PeriscopeModel*)model)->get_x == 0) return; ((PeriscopeModel*)model)->set_x(((PeriscopeModel*)model)->get_x+           atof((char*) Utilities::get_instance->get_config("PERISCOPE_SPEED").c_str)); ((PeriscopeModel*)model)->set_l_point(((PeriscopeModel*)model)->get_l_point+           atof((char*) Utilities::get_instance->get_config("PERISCOPE_SPEED").c_str)); ((PeriscopeModel*)model)->set_r_point(((PeriscopeModel*)model)->get_r_point+           atof((char*) Utilities::get_instance->get_config("PERISCOPE_SPEED").c_str)); }   if (d == LEFT) { if (((PeriscopeModel*)model)->get_x == -dbScreenWidth) return; ((PeriscopeModel*)model)->set_x(((PeriscopeModel*)model)->get_x-           atof((char*) Utilities::get_instance->get_config("PERISCOPE_SPEED").c_str)); ((PeriscopeModel*)model)->set_l_point(((PeriscopeModel*)model)->get_l_point-           atof((char*) Utilities::get_instance->get_config("PERISCOPE_SPEED").c_str)); ((PeriscopeModel*)model)->set_r_point(((PeriscopeModel*)model)->get_r_point-           atof((char*) Utilities::get_instance->get_config("PERISCOPE_SPEED").c_str)); } }

void PeriscopeController::run {   if (dbLeftKey) move(LEFT); if (dbRightKey) move(RIGHT); ((Periscope*)view)->print_coordinates; }

void PeriscopeController::update(Subject* s) { ((Periscope*)view)->move; }

/// ///   Periscope implementation /// void Periscope::initialize {   ((PeriscopeModel*)model)->set_x(-320.0); ((PeriscopeModel*)model)->set_y(-480.0); ((PeriscopeModel*)model)->set_l_point(160.0); ((PeriscopeModel*)model)->set_r_point(480.0);

Utilities::get_instance->create_object("periscope",       (char*) Utilities::get_instance->get_config("PERISCOPE_IMG").c_str,        SPRITE, ((PeriscopeModel*)model)->get_x, ((PeriscopeModel*)model)->get_y); dbSizeSprite(Utilities::get_instance->get_object("periscope"),       dbScreenWidth*2, dbScreenHeight*2); }

void Periscope::print_coordinates {   Utilities::get_instance->print((((PeriscopeModel*)model)->get_l_point+ (((PeriscopeModel*)model)->get_r_point- (((PeriscopeModel*)model)->get_l_point+70))/2)-20,       dbScreenHeight-20, Utilities::get_instance-> get_str(((PeriscopeModel*)model)->get_l_point)); Utilities::get_instance->print((((PeriscopeModel*)model)->get_l_point+ (((PeriscopeModel*)model)->get_r_point- ((PeriscopeModel*)model)->get_l_point)/2)+20,       dbScreenHeight-20, Utilities::get_instance-> get_str(((PeriscopeModel*)model)->get_r_point)); }

void Periscope::move {   dbSprite(Utilities::get_instance->get_object("periscope"),        ((PeriscopeModel*)model)->get_x,        ((PeriscopeModel*)model)->get_y,        Utilities::get_instance->get_object("periscope")); } code


 * //Periscope Graphical Implementation//**

Graphically the periscope was created as a separate layer placed over the game scene and manipulated via keyboard keys (Right-Key for movement to the right/Left-Key for movement to the left).



 =Info Panel Module=

Files: info_panel.h, info_panel.cpp

The info panel module is responsible for the game information presentation to the user. For example, when the periscope pointed at the particular battleship in the squadron, info panel displays battleship information to the user. Info panel also displays information about torpedoes shots, and number of battleships destroyed.

The info panel implementation is based on MVC pattern. It contains InfoPanelController class inherited from Controller class and InfoPanel class inherited from View class.

Info panel module declaration: code format="C++"
 * 1) ifndef _INFOPANEL_H_
 * 2) define _INFOPANEL_H_


 * 1) include "util.h"
 * 2) include "mvc.h"
 * 3) include "battleship.h"
 * 4) include "periscope.h"

class InfoPanel;

class InfoPanelController : public Controller { private: float ship_x; float periscope_l_point; float periscope_r_point;

public: InfoPanelController(Model* m);

void update(Subject* s); };

class InfoPanel : public View { public: void initialize; void print_ship_info(BattleShip* s); void print_game_info; };

code
 * 1) endif

Info panel module implementations: code format="C++"
 * 1) include "info_panel.h"

/// ///   InfoPanelController implementation ///

InfoPanelController::InfoPanelController(Model* m) : Controller(m) {   ship_x = periscope_l_point = periscope_r_point = 0.0; Utilities::get_instance->create_object("ship_info", "", SPRITE, 40, 60); dbSizeSprite(Utilities::get_instance->get_object("info_ship"), 200, 80); ((InfoPanel*)view)->initialize; }

void InfoPanelController::update(Subject* s) { std::list::iterator iter;

for(iter=subjects.begin; iter != subjects.end; ++iter) { if (s != (Subject*)* iter) continue; if (((Model*)s)->model_type == "ship") ship_x = ((BattleShip*)s)->x; if (((Model*)s)->model_type == "periscope") { periscope_l_point = ((PeriscopeModel*)s)->get_l_point; periscope_r_point = ((PeriscopeModel*)s)->get_r_point; }       if ((ship_x >= periscope_l_point) && (ship_x <= periscope_r_point)) ((InfoPanel*)view)->print_ship_info((BattleShip*)s); else dbPasteImage(-1, 40, 60); }

((InfoPanel*)view)->print_game_info; }

/// ///   InfoPanel implementation ///

void InfoPanel::initialize {   Utilities::get_instance->create_object("info_panel",        (char*) Utilities::get_instance->get_config("INFO_PANEL_IMG").c_str,        SPRITE, 0, 0); dbSizeSprite(Utilities::get_instance->get_object("info_panel"),       dbScreenWidth, 200); }

void InfoPanel::print_ship_info(BattleShip* s) { Utilities::get_instance->print(40, 40, "Ship:"); Utilities::get_instance->print(100, 40, (char*) s->name.c_str); dbPasteImage(s->id+1000, 40, 60, 1); }

void InfoPanel::print_game_info {   Utilities::get_instance->print(350, 40, "Torpedoes shots: 0"); Utilities::get_instance->print(350, 60, "Ships destroyed: 0"); } code

 =Model View Controller Implementation=

First, lets define what is model-view-controller. Here is brief explanation from wikipedia.

Model–View–Controller (MVC) is a software architecture, currently considered an architectural pattern used in software engineering. The pattern isolates "domain logic" (the application logic for the user) from input and presentation (GUI), permitting independent development, testing and maintenance of each.

The model is the domain-specific representation of the data upon which the application operates. Domain logic adds meaning to raw data (for example, calculating whether today is the user's birthday, or the totals, taxes, and shipping charges for shopping cart items). When a model changes its state, it notifies its associated views so they can refresh. Models are not data access objects; however, in very simple apps that have little domain logic there is no real distinction to be made.

The view renders the model into a form suitable for interaction, typically a user interface element. Multiple views can exist for a single model for different purposes.

The controller receives input and initiates a response by making calls on model objects.

The MVC pattern was very useful for the SeaFight game development as it allows quick and easy data-component interaction. For the best knowledge on MVC pattern please take the SSE 658 course. Here is my MVC implementation.

MVC declaration: code format="C++"
 * 1) ifndef _MVC_H_
 * 2) define _MVC_H_


 * 1) include

class Subject;

class Observer { public: virtual void update(Subject* s) = 0; };

class Subject { private: std::list observers;

public: virtual void subscribe(Observer* o); virtual void unsubscribe(Observer* o); virtual void notify; };

struct Model : public Subject {   std::string model_type;

Model { model_type = ""; }; };

class View;

class Controller : public Observer { protected: Model* model; View* view; std::list subjects;

public: Controller(Model* m); virtual ~Controller;

virtual void subscribe(Subject* s); virtual void unsubscribe(Subject* s); virtual void update(Subject* s) = 0; virtual void run {}; };

class View : public Observer { protected: Model* model; Controller* controller; std::list subjects;

public: View(Model* m, Controller* c); virtual ~View {};

virtual void subscribe(Subject* s); virtual void unsubscribe(Subject* s); virtual void update(Subject* s) {}; };

code
 * 1) endif

MVC implementation: code format="C++"
 * 1) include "mvc.h"

/// ///   Observer implementation /// void Subject::subscribe(Observer* o) { observers.push_back(o); } void Subject::unsubscribe(Observer* o) { observers.remove(o); }

void Subject::notify {   std::list<Observer*>::iterator iter;

for(iter=observers.begin; iter != observers.end; ++iter) ((Observer*)*iter)->update(this); }

/// ///   Controller implementation /// Controller::Controller(Model* m) { model = m;   view = new View(model, this); subscribe(model); }

Controller::~Controller {   delete view; }

void Controller::subscribe(Subject* s) { subjects.push_back(s); s->subscribe(this); }

void Controller::unsubscribe(Subject* s) { subjects.remove(s); s->unsubscribe(this); }

/// ///   View implementation /// View::View(Model* m, Controller* c) { model = m;   controller = c;    subscribe(model); }

void View::subscribe(Subject* s) { subjects.push_back(s); s->subscribe(this); }

void View::unsubscribe(Subject* s) { subjects.remove(s); s->unsubscribe(this); } code

The SeaFight game produces following result at the current stage:

Project files can be found at the course ftp server under my name. Remaining game parts for the project 3 will be torpedo functionality and shooting.

 =Dark GDK Surprises=

Dark GDK is powerful tool for games development. But it also contains surprises. One of them was dbStr function explained above. Another interesting behavior caused by Dark GDK implementation I did find when I minimized the game window and took a look in the Windows task manager. The CPU utilization was increased dramatically. Then I tried every sample game shipped with the Dark GDK, and behavior was the same. Once the game window minimized, the CPU utilization jumped to the maximum. Finally, I have found the thread at The Game Creators forum where the same behavior reported. I did not find any resolution for this problem so far and it seams to be a bug in the GDK.

 =References=

Wikipedia RGBA demogroup OpenGL committee

Last revision date: {$revisiondate}