Utilities
The blank solution (which can be downloaded), has two configurations, Debug and Release. In Debug mode, you are able to trace through the code step by step. The Release version is the final version and it produces much faster code.
Using C++, you as the programmer are responsible for allocating and releasing memory resources. Not taking care about memory allocations and messing up the heap (memory used for dynamic allocations) results in bad design and unstable code. To have a strategy avoiding memory leaks, helps writing solid code. To see what i mean, I first explain the term memory leak and then i show you how to avoid problems like these.
void func1() { int* ptrInt = new int; *ptrInt = 10; std::cout << "value of int value = " << *ptrInt << std::endl; } void main() { func1(); }
Function "func1( )" allocates data on the heap (32 bit), writes data into memory and prints it on the screen.
After that the function returns and the pointer to the allocated memory adress gets lost. The memory block
still exists. The left allocated data block (4 byte) is called a memory leak.
The engine helps you finding memory leaks very fast. All you have to do is add two lines of code, so that it
looks like this.
void func1() { int* ptrInt = SCE_NEW int; // added *ptrInt = 10; std::cout << "value of int value = " << *ptrInt << std::endl; } void main() { SCE_REPORT_MEM_LEAKS // added func1(); }
These two lines of code tell you where to find the memory leak in the code. (only in Debug!)
After closing the application (game), the logging system of the engine will write something like this:
Detected memory leaks! Dumping objects -> c:\source\main.cpp(88) : {131} normal block at 0x010E6678, 4 bytes long. Data: < > 0A 00 00 00 Object dump complete.
This heap dump gives you information about each memory block that has been allocated and not freed. It tells you in which file the data is allocated, the line number and size of the memory block!
Logging (Tracing) helps you keeping book about what is happening. Asserts help you to check for errors in
calculations, or to make sure the right parameters are passed to a function. The classical assert example is
to check, if a division by zero is going to happen.
Both mechanisms write the desired info into a file, stdout, the debug window of the compiler and
the game internal console.
Asserting is only enabled in Debug mode, Tracing works in both modes: Debug and Release.
void func1() { // logging (=tracing) SCE_TRACE("This is the start of function: func1\n"); int a = 1; int b = 2; int c = a+b; // check if calculated value is the desired one SCE_ASSERT_1(c == 3); SCE_ASSERT_2(c == 3, "Check variable c, it is not equal 3\n"); // inform user SCE_TRACE("End of function: func1, and the calculated value = " << c << "\n"); }
Every game has to handle a lot of data. The data can be geometry data, textures, audio files, scripts, ... Materials for example can take a couple of textures (bitmaps) and combine them, to define a new effect. Scripts can reference data files, set a couple of paramters and define for example a new particle effect. To make things and life easy, all the assets are stored in a predefined directory structure, that can be accessed and extended.
Using the blank solution from the download section, you will recognize that there are two major directories. One for the "source" code and one that is called "exec_game". The "exec_game" directory is the one of interest for now.
After compiling and linking in Debug (or Release) mode, you will find the following items in the "exec_game" directory (relative to this directory, all asset files are placed!) :
In the data directory the following six directories can be found:
Looking into these directories, you will find image, sound and graphics files. There are also all kind of different script files. These files can be opened with a normal ascii editor like notepad. You should take a few minutes and study these directories to get an overview.
The engine makes heavy use of scripting. Talking about scripts does not mean programming game or engine behaviour using a high level programming language like basic or java. Scripting here means: defining structures with parameters for all kind of objects like entities, materials, particles, ... . So the scritping is more like an advanced use of configuration files.
The engine has enough functionality to do comfortable scripting. I give you a couple of examples to show you how to use the classes and functions. (The concept is similar to that using XML, instead of using "< >" you use "{ }".)
Using configuration files and defining parameters in ascii files is common practice and it shortens development time. Instead of hardcoding (writing the configuration values into your source code and recompiling them everytime they change), use the engine scripting mechanism. Believe me it is worth the time spending a view minutes learning how to use it.
Taken from script file: "mech.cfg" located in the entity directory for entity mech. ----------------------------------------------------------------------------------- castShadows 1 pickable 1 graphicLodLevel_0 { model mech.graphModel view min 0 max 10 }
The script file holds two lines setting the flags: "castShadows" and "pickable". After that two parameters are collected in a block called: "graphicLodLevel_0". This block holds information about graphics data and viewing that graphics data. (For now there is no need to understand the params, the example should just show you the mechanisms of scripting.)
In the following i show you how to use the existing scripting functionality to read the values and how to handle blocks:
// first there have to be vars and structs that hold the values // i collect everything in a structure and call it Entity struct Entity { bool castShadowFlag = false; bool pickableFlag = false; struct GraphicLodModel { std::string file_; float minDistance_; float maxDistance_; }; GraphicLodModel grModel_; }; // loading code void loadGraphicLodModel(Script& script, GraphicsLodModel& grModel) { while( script.getCurrentLineIndex() != script.numOfLines() ) { LineInfo& info = script_.readLineInfo(script_.getCurrentLineIndex()); if( info.getNumTokens() == 2 && info.getToken(0) == "model" ) { grModel.file_ = info.getToken(1); } else if( info.getNumTokens() == 5 && info.getToken(0) == "view" ) { grModel.minDistance_ = atof(info.getToken(2).c_str()); grModel.maxDistance_ = atof(info.getToken(4).c_str()); } else if( info.getNumTokens() == 1 && info.getToken(0) == "}" ) // block is finished so return { return; } script.getCurrentLineIndex()++; } } void loadEntity(Entity* ent, const std::string& scriptFile) { Script script; script.open(scriptFile); while( script.getCurrentLineIndex() != script.numOfLines() ) { std::string token = script.readFirstLineToken(script.getCurrentLineIndex()); if( token.find("graphicLodLevel_") != std::string::npos ) { script.getCurrentLineIndex()++; loadGraphicLodModel(script, ent->grModel_); } else if( token == "castShadows" ) { script.readLine(script.getCurrentLineIndex(), token, ent->castShadowFlag); } else if( token == "pickable" ) { script.readLine(script.getCurrentLineIndex(), token, ent->pickableFlag); } script.getCurrentLineIndex()++; } }
// assuming the struct Entity is defined as above void saveEntity(Entity* ent, const std::string& fileName) { Script script; script.create(fileName); // write flags SCE_WRITE_LINE_TO_SCRIPT(script, ""); SCE_WRITE_LINE_TO_SCRIPT(script, "castShadows\t" << ent->castShadowFlag); SCE_WRITE_LINE_TO_SCRIPT(script, "pickable\t" << ent->pickableFlag); // write graphics data SCE_WRITE_LINE_TO_SCRIPT(script, "graphicLodLevel_" << 0); SCE_WRITE_LINE_TO_SCRIPT(script, "{"); SCE_WRITE_LINE_TO_SCRIPT(script, "\tmodel\t" << ent->grModel_->file << ".graphModel"); SCE_WRITE_LINE_TO_SCRIPT(script, "\tview\tmin " << ent->grModel_.minDistance_ << " max " << ent->grModel_.maxDistance_); SCE_WRITE_LINE_TO_SCRIPT(script, "}"); }
This looks like a lot of code, but you will see this is no rocket science and it is worth the time using it.