For my first tutorial, I wanted to start with a utility that I wrote a couple of weeks ago and has proved to be incredibly useful so far while developing HackyZack. It is always useful to have some sort of text drawing while debugging your game so that you can check the values of variables at runtime and figure out if something is working properly, or why it is not. Before, I just used to write some code within the object’s draw event and that worked, but I found myself writing very repetitive code over and over again, which I later had to go in and delete after it was working as intended. As a programmer, I knew there had to be a better solution to automate this process, which is why I decided to write what I call my Debug Logger.
The initial idea I had was simply to have a single function that would print out information to the screen. I wanted these messages to be on screen for a certain amount of time and then disappear. In order to accomplish it, I created a persistent object called oDebugLogger that will manage the list of messages and print them to the screen. So let’s create an object and make sure the Persistent and Visible flags are on, since it’s really important. Also, make the depth be something like -10000, since we want this object to be able to draw on top of everything else.
We’ll start simple and then keep adding functionality and features as we go on. We first need to add a Create Event and in a code block, let’s declare a variable where we will store our list. Note: I will name object-scope variables with an underscore (_) in the beginning of the name and just lowercase to event-scope variables (the ones you declare with var in front). This is how it should look like:
/// Variable Initialization // Messages list _msgList = ds_list_create();
Pretty simple for now. The other thing we need to do is create a Draw GUI event and just print all the messages currently in the list. Here’s how I did it:
/// Print messages var yOffset = 16; // Vertical space between messages var pos = 0; // Current position in the queue of messages for (var i = 0; i < ds_list_size(_msgList); ++i) { draw_text(8, 8 + pos++ * yOffset, _msgList[| i]); }
Basically, I go through all the elements in the list and I print them one under the other. Now let’s jump right into writing the script that you will want to call in your game to print messages. Create a script called DebugLog and fill it with this:
/// DebugLog(message) // Check that the object exists if (instance_exists(oDebugLogger)) { with(oDebugLogger) { ds_list_add(_msgList, argument0); // Add message to the list } }
We do some safe-checking to make sure the object is created and then we proceed to add the message passed in as an argument to the list. Now, in order to test this feature (and the ones I will implement in the future), I have created a global object that controls everything that happens in the game, which I will call oGame. In it’s create event, I have written this:
/// Global Object Instantiation _debugMode = true; // Flag to trigger debug mode features if (_debugMode) instance_create(0, 0, oDebugLogger);
The _debugMode flag is useful to perform operations that are useful while developing the game, but don’t want to see them in the final product (cheat codes, debug text, etc). It also makes it so that you don’t have to go through your code before release trying to hunt down all these debug messages and features you added.
Last thing we need to add is a way to test the tool. For this matter, I created a Step event with some very basic input checks that will be print out specific messages.
/// Debug Logger tests var kSpace = keyboard_check_pressed(vk_space); var kOne = keyboard_check_pressed(vk_numpad1); var kTwo = keyboard_check_pressed(vk_numpad2); var kThree = keyboard_check_pressed(vk_numpad3); if (kSpace) DebugLog("Space"); if (kOne) DebugLog("Num1"); if (kTwo) DebugLog("Num2"); if (kThree) DebugLog("Num3");
Now, all that’s left is to create a room (which I called rmDebugLogger) and simply add the oGame object to the room. This is what your current program should look like while pressing Space and the first three NumPad keys.
This is all fine and working as expected. The issue now is that our messages are never getting deleted, so we will soon overflow the screen space. There are a couple of things we should to to accomplish this. First, we must set a maximum number of messages that can be displayed at once. For this purpose, we create a new variable in the oDebugLogger create event. We also create a new variable that will determine the amount of time that messages will be displayed for. When I first implemented this, I had a a global timer that would reset every time a new message was pushed to the list. Later, I realized that it would be better to have individual timers for each message, so I created another list that matches the one for the messages with their timeouts. Here’s what the create event looks like with all these changes:
/// Variable Initialization // Constants _maxMessages = 8; // Maximum number of messages to be displayed _msgLifespan = 5 * room_speed; // Time each message will be displayed for // List of messages _msgList = ds_list_create(); _msgListTimeout = ds_list_create();
We should also update the DebugLog script in order to add the timeout of the messages as they are added to the list.
/// DebugLog(message) // Check that the object exists if (instance_exists(oDebugLogger)) { with(oDebugLogger) { ds_list_add(_msgList, argument0); // Add message to the list ds_list_add(_msgListTimeout, _msgLifespan); // Set the initial timeout } }
Now, we must properly update the lists at every step. The timeouts should be updated in the Begin Step event like this:
/// Update timeouts for (var i = 0; i < ds_list_size(_msgListTimeout); ++i) { --_msgListTimeout[| i]; }
Also, create a Step Event and we will add code to remove elements from the list in both cases: When the time is exceeded for each message and when the maximum number of messages has been reached.
/// Update lists // Remove elements if there are more than the max number of messages while (ds_list_size(_msgList) > _maxMessages) { ds_list_delete(_msgList, 0); ds_list_delete(_msgListTimeout, 0); } // Remove top message if timeout time was reached for (var i = 0; i < ds_list_size(_msgListTimeout); ++i) { if (_msgListTimeout[| i] <= 0) { ds_list_delete(_msgList, i); ds_list_delete(_msgListTimeout, i); } }
If we run the application again, everything should be working as expected. This is what it looks like at this stage.
That is all for now! In my next post, I will extend the functionality of this Debug Logger to make it more useful, such as providing ways of having static messages, different colors, modify the position, among other things. Remember that if you wish, you can download the entirety of the code from the repository link I posted above.
Until next time!