Introduction
Now that we have terrain that can interact with worms and can be deformed nicely, this week I decided to work on some QoL improvements.
The first thing that I want to talk about is something i’ve been slowly making over the last couple weeks, my debug logger.
Debug Logger 2.0
The first version of my debug logger was basically a set of if statements that people would have to make every time they wanted to std::cout a message to the console where the if statement would succeed if a variable in that system’s header was true or false. The idea behind this was that people would be able to toggle on and off certain sections of debugging which would remove unwanted messages which were unrelated to what they were developing.
The problem with this system came in two parts: 1 - People were forced to bounce around the code base and having to spend too much time finding and turning on/off the systems variables. 2 - People had to add a large amount of extra characters when debugging.
My aim was to make a debug logger that solved both of those issues and thus be much more easier and user friendly.
To do this the first thing I had to do is structure it in some way, so I made a class named DebugLogger and decided I wanted this to contain a set of functions you would call to output messages as well as contain the variables that required toggling.
I looked at the debugger that came with our IDE to see if it did something that would be useful for my debugging system and found that it used a 3 part system where it broke its outputs into either a message, a warning or an error. I decided I would go for a similar approach and allow the user to actually toggle which layers they would want to see. Our message layer would be used for standard outputs and would be non-important information, the warning layer would be used for high priority messages and things deemed important and finally the error layer would be saved for critical messages that no matter what system you are working on would be of importance.
Using this layered technique meant that if you were working on one system you could safely turn off the message layer of a separate system without worrying about missing anything critical, and if it did break then its error message would still get through.
I used a map to lay out this toggling section like so:
{
{ WORM_FEATURE,
{
{MESSAGE, true},
{WARNING, true},
{ERROR, true}
}
},
{ GAME_FUNCTION,
{
{MESSAGE, true},
{WARNING, true},
{ERROR, true}
}
},
{ PHYSICS,
{
{MESSAGE, true},
{WARNING, true},
{ERROR, true}
}
},
{ TERRAIN,
{
{MESSAGE, true},
{WARNING, true},
{ERROR, true}
}
},
{ WEAPONS,
{
{MESSAGE, true},
{WARNING, true},
{ERROR, true}
}
},
{ COLLISIONS,
{
{MESSAGE, true},
{WARNING, true},
{ERROR, true}
}
}
};
This meant that we had solved problem one. Now for the second problem, we would need to make those functions are user friendly as possible.
At first I came up with this solution:
void writeLine(outputLocations _location, outputTypes _type, std::string _message);
This function would take a location from above as well as the layer you wanted to display it at and the message you want to send, then it would format it for you and display it with the filter applied.
The problem with that was the following:
If you wanted to use this in your code to output one thing it would actually look like this:
debug->writeLine(DebugLogger::outputLocations::COLLISIONS, DebugLogger::outputTypes::MESSAGE, ("Collision debug messages active"));
This was really upsetting because it was longer than using the if statement.
Thankfully after talking to the group we realised that we could remove which layer we wanted if we made a set of functions that act as presets.
void DebugLogger::writeMessage(outputLocations _Location, std::string _message)
{
writeLine(_Location, MESSAGE, _message);
}
void DebugLogger::writeWarning(outputLocations _Location, std::string _message)
{
writeLine(_Location, WARNING, _message);
}
void DebugLogger::writeError(outputLocations _Location, std::string _message)
{
writeLine(_Location, ERROR, _message);
}
These functions reduced the character count dramatically refining the previous statement to this:
debug->writeMessage(DebugLogger::COLLISIONS, ("Collision debug messages active"));
The team and I are happy with this method and I think this as far as we can go to shorten it.
As for the formatting side of things the write line code looks like this:
void DebugLogger::writeLine(outputLocations _location, outputTypes _type, std::string _message)
{
#ifdef _DEBUG
if (visableMessages[_location][_type])
std::cout << "[" << location_names[(int)_location] << " -> " << output_names[(int)_type] << "] " << _message << std::endl;
#else
if (_type == ERROR)
std::cout << "[" << location_names[(int)_location] << " -> " << output_names[(int)_type] << "] " << _message << std::endl;
#endif // Runs comments as expected in debug mode, only logs error level content in non debug modes :)
}
It’s quite interesting because it uses these fancy preprocessor statements to change what code should be executed based on what build we run the game in.
This actually means that when running in anything other than debug mode our code will only show Error level statements.
The function will check if the message sent is visible on the current filter and then print out a formatted message.
Here is the end result:
From this:
To this:
(The above image is a slightly old screenshot of the collision generation and uses more threads than the current version)
Overall I think that this debugger has worked really well!
Stamps and Backboards
Stamps and backboards have been something on my todo list for some time.
The Theory
The idea behind the backboard is to have an image behind the render target that is slightly darker so that when the players destroy bits of the terrain you create a crater effect.
Stamps on the other hand are objects on the terrain that seem like they are just behind the terrain but are on the render target (and thus solid/deformable). They have no backboard which makes sense as they are not objects with depth. An example would be a tree.
The Implementation
The backboard is nice and simple, all we do is when we take the background image file path look for the same name with Back concatenated on the end and generate that as the background image and draw it before we draw the RT so it appears behind it:
string terrain_background_path = _fileName + "Back";
terrain_background = new ImageGO2D(terrain_background_path, _pd3dDevice);
As for the stamps, that is a little bit harder.
The issue is we want the stamps to appear behind the terrain. I tried making a special blendstate to stamp the shape I wanted onto the terrain and cut off anything that would have been drawn on a pixel with an alpha value above 0 but I couldn’t figure it out, so instead the only option I have is to draw them just before we draw the actual terrain into the RT. I used the same queue technique as I did with the deformation system meaning that to add the stamps you only to need to write a single line like this:
m_terrainData->addStampToTerrain("testStamp", m_d3dDevice.Get(), Vector2(500, 330));
Then in the first render frame where we draw the terrain into the RT just before we do this we check if the queue is empty, and if it’s not we simply draw each image onto the RT with the basic Opaque blendstate.
What we end up with is something that looks like this: