Introduction
In this tutorial we are going to show you how to use the built in Debugger. You can also watch this video on it:
This is a tool that is used for "debugging" your game, which simply means running your game within a special framework where the inner workings of the project are exposed for you to check and, in some cases, change. Debugging is an essential part of creating any project, particularly large ones, and can be used to step through your code a line at a time as it runs, change variable values to see what effect it has, find errors and even optimise how the final project will run.
This tutorial uses the YoYo Games Dungeon demo project and should be fine to run for all licence types, and you should be able to press the Play button in the IDE and test the project normally. Once you've tested that it runs and everything is okay, you can then run the project again, but this time using the Debugger. Do this now by clicking the Debug button at the top of the IDE, then continue to the next section of the tutorial.
Overview
Before we go into any depth about the various different debugger features, let's first get familiar with the tools available and where they are within the GameMaker Studio 2 UI by default. When you first run a project in debug mode, the GameMaker Studio 2 workspace will change to the Debugger Workspace which adds various tabs to the different docks, with the workspace empty in the middle to start with. Below you can see an image of the default debugger view with the relevant parts labelled:
Here's a brief run-down of each of those items that have been labelled:
- Graph View - The Graph View shows a nice graphic representation of the current and average memory use as well as the current and average FPS over time, and you can zoom in or out using the / along with the mouse wheel , and if you position the mouse cursor over any of the graph elements, it will show you the value for that element at the selected point. Note that if you use the functions show_debug_message() or debug_event() then these will also be shown in the Graph view.
- Resource View - The Resource View shows you a list of all the resources being used in the project that can contain code or DnD™ actions. This section works much like the Resource Tree and you can click on the "expand" button to expand the items in it to show more information, for example, the different events for a specific object. You can then double click on any of the resources to open the code for it in the Code View window.
- Call Stack - This window will display the current call stack of events, as well as the line number for the code being stepped through. The debugger will need to have been Paused before any information will be shown here.
- Tool Bar - The toolbar has the debugger controls along with some basic information such as memory use, the current (real) FPS for the game and an indicator to show whether the debugger is currently connected or not (the indicator will be green when connected to a running instance of your project and red otherwise. We'll go into what the buttons do in more detail further on in the tutorial.
- Code View - The code view is where we can see and review different scripts or event code from our project as it runs. By default this starts empty, but you can double click on any resource from the Resource View to open it here. Note that you can open multiple different resources and they will all be added to the Code View workspace as tabs along the top, and you can also add and remove breakpoints here (we'll discuss breakpoints further on in this tutorial).
- Watches - This section of the Output Dock is dedicated to the different watches for the Debugger. A watch is simply a panel that "watches" something, whether it be an instance, a single variable, a buffer or even the surfaces and textures in use. We'll be discussing the different watch panels individually as we go through this tutorial.
Now that you know what everything is, let's go into a bit more depth about what everything does, starting with the Toolbar and stepping into, over and out-of your code...
The Toolbar
The ToolBar is where you can find the main Debugger controls, along with some basic information about the project. This information includes the memory use, the current (real) FPS for the game and an indicator to show whether the debugger is currently connected or not (the indicator will be green when connected to a running instance of your project and red otherwise).
Let's quickly run through the controls you have available to you:
This starts the game again after it has been paused (keyboard shortcut f5). | |
This will pause (break) the game. | |
Restarts the game. | |
Stops the game. | |
Step into a code block or script (keyboard shortcut f11). | |
Step over a code block or script (keyboard shortcut f10). | |
Step out of a code block or script (keyboard shortcut + f11). | |
Turn on or off real time updates for the debug information (this is off by default). | |
The first three buttons are pretty self explanatory and are used to Play, Pause and Restart your game. You'll be using the play and pause buttons most, as only when the game is paused can you perform certain actions, and if you use breakpoints (more on them later) then those will pause the game automatically for you. You can also stop the game at any time using the Stop button, but you will need to hit the Debug Run button again to start it, as stopping the game will lose the connection between it and the Debugger.
The next set of buttons are for "stepping" through your code and they will only be available when the game is paused. The first button, Step In , is for you to go through the code from any position one step at a time, running each line of code as it would be run in the game one at a time. Using Step In on a script call will open the script in a new tab in the Code View and continue to step through each line of the script before going back to the code/event that called it and continuing. Now, if you don't want to step into a script like that, you can click the Step Over button and the script will be run entirely before the debugger stops at the next line in the code that called the script. If you want to leave the script before it has finished being run, or the code is a loop and you want to exit the loop and continue with the rest of the code, then you can use the Step Out button . Stepping out of a code block will run the rest of the block (or script) before pausing again on the next line after. The following GIF illustrates these features:
If you have run the included game project (if you haven't then click the Debug Run button now), you'll see the debugger workspace open and the game will run as normal. You can now pause the game using the debugger at any point and click through the code using the Step In, Out and Over buttons in the IDE. It's pretty nice to be able to step through your code in this way, and as you'll see it permits you to access more powerful features. However before we go on to talk about that, we need to talk about breakpoints...
Breakpoints
Being able to pause the debugger and check your code is a nice tool, but it's not very helpful if you can't stop it exactly where you want, when you want. Stepping through a dozen events and scripts to get to the code that is of interest isn't much fun... which is why we have breakpoints. Simply put, a breakpoint is a flag in your code that tells the debugger "Pause the game here!". So, how do we add breakpoints?
You have two kinds of breakpoints that can be added to your code: temporary ones and persistent ones. You can add or remove temporary breakpoints at any time and in any code or script block by left clicking in the gutter (where the line numbers are), or you can use the right mouse button to open the Context Menu, which also permits you to add or remove breakpoints:
Now the game will pause everytime the debugger reaches the breakpoint that you have added and you can step through your. Note that we consider breakpoints added in the Debugger to be temporary because they are not stored with the project and will be lost when you stop the debugger.
Breakpoints can also be set from the GameMaker Studio 2 IDE. When writing your project, they can be added or removed at any time using f9 (or using the right mouse button and selecting "toggle breakpoint"), either on a DnD™ action in the object window, or on a line in a code editor window (for events or scripts). Now, when you run your game in debug mode it will pause at the point (or points) that you have defined. Breakpoints set from the IDE are saved along with your project and will persist over various runs until removed again
It's worth noting that all breakpoints will appear in the Output Window where they can be enabled or disabled or even removed.
Now you know all about breakpoints and stepping through your code, let's move on and discuss the various different watch views available...
Variables: Local
The first of the watch windows we are going to look at is the one for Variables:
This window view shows three different variable types that can be watched, Local, Global and Watches. When you first start the Debugger, these windows will be empty as they only update when the game is paused or (in the case of Global variables) when you have clicked the Realtime Debugging button on the Debugger ToolBar (this button will update some watches in real time as the game progresses).
If you have the Debugger running now, pause it using the Pause button if it's not already paused, and you'll see that the Local and Global windows will be populated with information. In the case of the Local window, this will show all the variables local to the current instance being stepped through in the debugger. For example, if you add a breakpoint to any event within the object "oPlayer" and then run in Debug mode, the Debugger Local window will show all the variables local to the player instance:
As you can see, the window now shows the object and event as a "ScriptName", as well as the other headings ".PC", ".Self", and ".Other". The important one to start with is the ".Self" section as it contains all the variables currently assigned to the instance. If you click on the expand button then the view will expand to show all the custom variables that the instance has (ie: the ones you have added to the code) as well as a further expandable section with the built-in variables (ie: all those variables that GameMaker Studio 2 includes in all instances). If your instance is in a collision or the code being stepped through is within a "with" function, then the ".Other" section will show the built-in and local variables for the other instance in the collision, or for the instance calling the "with" function.
Variables: Global
The Global window shows all the variables that your project has declared as Global in scope:
With the test project you can see that we have several global variables, and those that are arrays have been marked for you and can be expanded to see their contents by clicking the expand icon . However, while the debugger can tell the difference between certain variables types it can't tell whether a given integer value for a variable is a data structure or an instance or simply a number. Data structure IDs are given as integer values starting from 0, while instance IDs are integer values starting from 100000, so if you have a variable (local or global) that has an integer value then you may need to tell the debugger what the value represents using the right mouse button menu:
To see how this works, pause the debugger and then click on the "ot" global variable to select it. Once selected, use the right mouse button to open the type window, and select "DS Grid". The debugger will now show the width and height of the DS grid stored in the global "ot" variable and you can expand this to see individual values:
Variables: Watches
Finally you have the Watch window:
The watch window is where you can define your custom variable watches. Instead of having to go through the instance windows or the local or global variable windows, you can instead input a variable to watch here, and the debugger will update it. How you add the variable to watch will depend on the type of variable that you are watching and the scope of the variable:
- local variables: If you want to watch a local variable (one that has been declared using the var keyword) then you can simply add the variable name and when the debugger is in the event or script that uses the variable, the value will be updated.
- global variables: To watch a global variable, you must first prefix the global keyword to the variable name, for example: global.score.
- instance variables: If you want to watch the variables for a specific instance, even when that instance is not the current step of the debugger, then you need to prefix the variable name with the instance ID value. You can get the ID value from the debugger as it steps through the code, or you can pause the debugger and get the ID value from the "Instances" windows (more on these later). Note that to evaluate the variable correctly, the ID value needs to be enclosed in (), like this: (1000005).xprevious.
As with other watches, you can click the right mouse button on any watch to change it's type to show data structures etc... and the right mouse menu also permits you to clear a watch or clear all of them.
It's important to note that the debugger doesn't just permit you to see the values of variables, but you can also change them too. We'll show how this can be done in the next section on the Instances windows...
Instances
Now you know what the Variables windows are for, let's look at the Instances windows:
There are three windows for Instances:
- Instance: This shows the variables, both local and instance scope, that belong to the instance that is currently the focus of the debugger. As you step through the code of the project, if the current instance changes then so will the debugger values shown here.
- All Instances: This window shows a list of all the instances in your game, and each one can be expanded by click on the expand button to show the instance scope variables that it contains (local scope vars will only be shown when the instance is currently being stepped through).
- Selected Instance: This window is special in that it will be blank when you first start the game and will only be populated when you select an instance to show from the actual game window. To select an instance you must first pause the debugger, and then go to the game window and simply click on the instance in view that you want to watch.
With the Variables windows and the Instances windows you have multiple ways to view the contents of all the different variables in your game, but not only can you see them, you can edit them too. If you pause the debugger and then select an instance to watch, you can then double click on the value for any variable shown and change it to something else, and when you restart the game in the debugger, the new value will be used. Let's test that now by doing the following:
- First run the project in debug mode and then click on the Pause button .
- Now, go to the game window, and click on a Chest object that you can see in the view.
- If you go back to the debugger, the "Selected Instance" window will now be populated with different variables and their values. Click on the Expand button for the built in variables to see them.
- Finally, find the "x" variable and double click on the value it has then change it to 0 and hit "Enter".
If all has gone correctly, when you press play on the Debugger, the chest will move and no longer be visible in the view. You can pause the Debugger and set it back to it's position again if you wish, but note that whatever changes you make to a variable here will not be permanent and will be lost when you close the debugger or restart the game. Being able to edit values like this is very powerful and lets you test different things before actually implementing them in your code.
Graphics
The Graphics window is where you can get information about the current render state of the game as well as the different textures and surfaces that are being used:
If you haven't selected this tab from the output dock then do it now. You can see that the first window simply shows the current render state, which means that it will contain all the information relevant to how the game is being displayed by the hardware running it. You can't edit or change anything here, but it's useful information to have when trying to work with the graphics pipeline.
In the other window we have the Surfaces and Textures that are being used in your game. Like the render states window, there isn't much here that you can edit, but it's very handy to know exactly what textures have been made by your game and what surfaces are currently in use. Many times you can optimise a project by simply changing how the base sprites are stored on the texture pages, and this window can help with that (for example).
By default the window will show only the textures that are in use but you can click the button at the bottom of the window to open up a selector where you can choose to view surfaces instead. You can also mouse over the Thumbnail image to open up a larger version of the texture or surface so you can see the details it contains and also the size in pixels. Take a moment to explore this window and see how the things in your game are being rendered, as it can sometimes be quite surprising!
We've nearly reached the end of this tutorial, but there is one further tab in the Debug output that we want to look at, the Other tab...
Other: Buffers
Let's now take a brief look at the Other windows, specifically the one for buffers. If you click the Other tab now with the debugger paused it should look something like this:
The main dungeon demo doesn't use buffers, so you should start the game in the debugger again and then press "Backspace" to skip to the special Debug Room that we've added for this tutorial. This room has a single debug object and some tiles.
If you pause the debugger now, you should see that the Buffers window is populated by different values (which don't seem to make much sense, but we will fix that and make it easier to understand). Let's quickly go through the different elements of the buffer window:
The first thing to note is the Buffer Id Value. As buffers are created they are assigned an integer ID value starting at 0, so you can use this selector to skip through the buffer IDs to see what each contains. The next option is the Column Selector, which gives you a choice of how to display the different columns of data. Finally, we have the Data Display controller. This is where you can change how the buffer data is interpreted for displaying, and so make it easier to understand and read the buffer contents.
In our debug room, all we've done is take the index of each tile in the tile layer and added it into a buffer (if you open the Create Event of the object "oDebugger" in the Code View of the debugger you can see the code). If you look at the buffer contents in the Buffers window though, the values don't look much like tile indices! thsi si because we haven't formatted the data display properly, so click the Data Display button to open the display options menu (or use anywhere in the Buffers window).
Our buffer uses the u8 data type, so we need to set the buffer to display this correctly by first selecting the 1-byte integer option, then we need to select the unsigned option. This will change the data display to show the correct values and make them readable:
Note that in the GIF above, the cursor highlights the first number which is "79". If all has gone correctly, this should correspond to the tile index 79 on the tile sprite, which in turn should be the tile that was used in the top left corner of the room. Check it and see!
Other: Profiling
The last (but certainly not least) part of the Debugger, is the Profile window. For the profile window to work, you need to first make sure the Debugger is running the game and not paused, and then you need to click the "Start Profiling" button. Click this now, and it will then populate the Profile window with different details about your games performance and how long certain things take compared to others, etc... We'll break down the Profile window as follows:
- Enable / Disable: This is the button to enable or disable the Profiler. When enabled, the profiler will start and you will start to see events, scripts and functions be listed in the main window along with different data values for each of them. How this data is displayed will depend on the Time Display and the View Mode (both explained further down this page).
- Time Display: This checkbox permits you to switch between the average time and the absolute time for the profiler. When it is not checked, the values shown in the main window will be the total number of calls made to the function, script or event and the total time taken (times are in microseconds) over the course of the profile. However if you enable this then these columns show the average time taken per step for each function, script or event, and the average number of times that it was called.
- View Mode: This menu lets you choose the view mode, which can be either Top Down or Bottom Up:
- Selecting the Top Down view mode will show the profile from the top down (more or less the same as the callstack hierarchy) so it will show the event, then the script and then any functions used. Note that you can double click on any of the entries to have it open in the Source debug window if you have one. If you see a icon beside a name then there are further script or function calls within that section, and clicking the will expand the tree to show them.
- Selecting the Bottom Up view mode will show things from the bottom up so you can see all the functions and script calls individually. When using this view mode, clicking on the will expand the function or script to show what actually called it.
- View Target: The View Target permits you to choose between viewing the project GML (event and code data), the background engine processes or both. If you choose "GML", then you will get the events, functions and scripts being called each step while the "Engine" view will only show the engine calls required by the project, enabling you to see any bottlenecks in the way that your project is handling things. If you select "Both", then both sets of data will be displayed, but note that when combined with the "Bottom Up" view, the different script and function calls will be nested within the engine calls and you will need to click the to expand these out to see them.
Okay, so this window basically gives you a breakdown of how each component function and script of your game performs. Let's see it in action by profiling the Debug room in our tutorial project. If you aren't already running the debug room, press "Backspace" now to go there, then start the profiler.
The room only has one object to debug, and we're using it to see whether there is a difference in performance between doing a distance check with the function point_distance() or doing a check within the radius of a circle using the function point_in_circle(). This is a very simple check to do, but it will give a good idea of how the profiler works in a practical example (note that we are calling each function 200 times per step to speed up the profiling!).
When you start the profiler, you should select "Top Down" and "My Code" for the display options, and then click the expand button on the "oDebugger(step)" option so we can see the functions it contains. To start with, the profiler shows they are pretty much equal, but as time goes by, you should start to see a difference between them, with one being a tiny bit faster to use than the other...
The Profile window is a very powerful tool when debugging your game and it can highlight all sorts of problems within the project code as well as show where any "bottlenecks" are in the engine or rendering. It's worth noting though that you shouldn't get too into micro optimising using the profiler! If your game runs at a few hundred FPS and you're happy with it, then just get on and make it. Over-optimising is a sure-fire way to get bogged down in meaningless details, waste time, break your code, or render your code unreadable!
Summary
The Debugger can seem a bit daunting when you first start it up, but if you've read through this tutorial you should now have a firm grasp of the following:
- How to watch different variables within your game code
- How to watch specific instances and their variables
- How to change the type of value being watched from a real to a data structure
- How to edit variable values while the game is paused
- How to find out more about the render states, buffers, and textures / surfaces
- How to use the profiler
Using the debugger as you create your game projects is essential, not just to find errors, but to help optimise your projects and find potential issues, and we hope that after going through this tutorial you'll be able to get started using it without too many issues. Before closing this tutorial, we recommend that you take some time to "play" with the debugger and experiment with changing variables, adding breakpoints, stepping through the code, etc... You should also try making small test projects that focus on certain features (like buffers, or surfaces) and then running them through the Debugger to understand better the individual components.