Useful Tools for Programmers - Game Programming Algorithms and Techniques: A Platform-Agnostic Approach (2014)

Game Programming Algorithms and Techniques: A Platform-Agnostic Approach (2014)

Appendix B. Useful Tools for Programmers

There are several tools that make programming less error prone and improve the overall ability of a programmer. This appendix covers some of the most valuable tools—the ones most professional developers will use on a daily basis. These tools are general enough that they aren’t exclusively for game programmers, so any programmer can benefit from learning to use them.

Debugger

Novice programmers often try to debug problems in their code with text output statements, using something like printf in C or cout in C++. For a console application, this might make a lot of sense. If a user is required to type “yes” or “no” and instead enters “maybe,” it’s completely appropriate to have a text error message. But using print statements in real-time programs such as a game typically does not work too well.

Imagine you are trying to fix a problem with the position of a particular game object. It might be tempting to add a print statement in this object’s Update function that outputs the position of the object every frame. But the problem with this approach is you will now have console output 60 times per second with the position of the object (assuming the game runs at 60 FPS). It would be difficult to extract meaningful information from that amount of spam—and worst of all, all that spam can slow down the frame rate.

That’s not to say that print messages don’t have any value in a game. If it’s an error that only occurs once, such as a nonfatal inability to load a texture, it might be okay to output that as a text error. But an even better solution would be to actually show a temporary texture in-game, in hot pink or another neon color, so the problem is instantly visualized by anyone playing the game. The fact of the matter is that other team members likely are not going to pay attention to text console output, but they definitely will notice a hot pink texture.

So if print messages aren’t a good way to debug a game (or most programs), what should be used? The answer is the debugger. Every single IDE (integrated development environment) has some sort of debugger built in. Some work better than others, but the basic principles are typically the same. This section looks at the debugger in Visual Studio 2010, but all of the concepts should transfer over to the IDE of your choosing (though, of course, the menus and key presses will be different). The screenshots specifically show the code from the tower defense game in Chapter 14, “Sample Game: Tower Defense for PC/Mac.” In order to debug that game, you will need to put it into windowed mode, which can be done by setting GlobalDefines.bFullScreen to false.

Basic Breakpoint

A breakpoint on a line of code tells the debugger that whenever it reaches that particular line, it should pause the program. To set a breakpoint in Visual Studio, move the cursor to the line you’d like the breakpoint on and press F9 (alternatively, you can click the grey column on the left side of the editor window). When a breakpoint is hit, you can mouse over variables to see their current value. A breakpoint in the Update function of Enemy.cs is shown in Figure B.1.

Image

Figure B.1 A basic breakpoint in Visual Studio.

Once you hit a particular breakpoint, it’s useful to be able to step through the code line by line, so that you can pinpoint exactly where the code goes wrong. There are two different ways to step through the code. If you use Step Over (by pressing F10), the code will continue to the next linewithout going into the code of any functions called on the current line. So, for example, suppose you had the following two lines of code, and the breakpoint was on the first line:

myFunction();
x = 5;

If you were to step over the first line, you would immediately jump to the second line, and only see the results of the myFunction() call. You wouldn’t see any of the code that was executed inside of the function. If you do want to jump into a particular function and go through it line by line, you can use Step Into (by pressing F11). This will go into any functions on the line, in the order in which they are called. If you step into a function and realize you don’t actually care about the line-by-line behavior of that particular function, you can jump out of it using Step Out (by pressing Shift+F11).

Watch

A watch allows you to track specific variables, without needing to always mouse over them to check their values. The easiest way to add a watch is to right-click a particular variable in the debugger and select Add Watch. When debugging, you will then have a watch window at the bottom of the IDE that shows the current value of the variable. If the variable is a struct or class, you may need to click the + symbol to expand it in order to see the member variables. As you step through lines of code, whenever a watch variable changes, the color of the variable will change to red. This is an easy way to eyeball what values are changing over time. A sample watch is shown in Figure B.2.

Image

Figure B.2 Variable watch in Visual Studio.

There are also two automatic watch windows in Visual Studio: Locals and Autos. Locals refers to any variables that are active in the current scope. If you’re inside a class in C# or C++, one of the local variables is always going to be the this pointer. The Autos watch list is a bit more restrictive, and only includes variables that are actively being processed by the lines of code relatively close to the current one.

One important thing to note is that all types of watches will work in a “Debug” build, but may not work in a “Release” build, depending on the language. That’s because a Release build is optimized, and one of the optimizations is to remove any information that the debugger uses to track variables. This is especially noticeable in C++, which has aggressive optimization routines in Release.

For AAA games, a common issue is that the Debug build is literally unplayable. If you have a game that’s pushing a system to the brink, it’s not uncommon to have a Debug build run at less than 5 FPS, which really doesn’t allow for effective testing. One solution to this problem is to have an in-between build that has some of the debug functionality enabled, but also some of the optimizations. Another option is to run in Release, but turn off optimization for the particular file you are trying to debug. In Visual Studio, you can right-click the file in question in the Solution Explorer and turn off optimizations specifically for that file. But don’t forget to change the settings back once you’re done debugging!

Call Stack

The call stack window shows you which functions called the function that the debugger is currently in. This is especially helpful in the case of an unhandled exception or an outright crash. When you get a crash in Visual Studio, you will see a dialog box with a Break button. If you click the Break button, the debugger will pause right at the point of the crash. This allows you to inspect the values of variables as well as see what was on the call stack when the crash happened. The latter is especially important when you can’t figure out why a particular function was called at a particular point.

What you definitely should not do when you encounter a crash is put print statements everywhere to try to pinpoint where the crash happen. This is an absolutely terrible (and often unreliable) way to find the source of a crash. You have a debugger designed to help with this problem, so you should most definitely use it.

Breakpoint Conditions

An issue with putting a breakpoint in a function that gets called single every frame is that the game will pause at that line over and over again. But suppose you only want to pause at the breakpoint when a particular condition is true, like for instance when a particular member variable is zero. This is where conditional breakpoints come into play. To add a condition to a breakpoint, right-click the red breakpoint circle and select Condition. You then will get a dialog box in which you can type the particular condition, as shown in Figure B.3.

Image

Figure B.3 Setting a conditional breakpoint in Visual Studio.

Once the conditional breakpoint is set, you then will only stop at the line when the condition is met. This is a really handy way to get a break to only occur when you need it to. One other type of condition you can have is a hit count condition, which means that the breakpoint will only pause in the debugger once it has been hit the requested number of times. To use a hit count, you right-click a breakpoint and select Hit Count.

Data Breakpoint

Suppose in a C++ game you have a member variable called m_Alive that tracks whether the particular game object is alive or dead. While debugging, you notice that sometimes the m_Alive variable is changing even though the object in question never called the Die function to set it tofalse. This has to mean that somewhere and somehow, the memory location that stores m_Alive is being clobbered by bad values. This can be one of the most frustrating types of bugs to solve, because the culprit could be a bad pointer access literally anywhere else in the code.

One feature that’s immensely useful to solve this problem in Visual Studio is the data breakpoint, a type of breakpoint that only works in C/C++. A data breakpoint tells the debugger to pause the program whenever a specific range of memory is modified. The size of the memory range typically is 4 bytes on 32-bit systems and 8 bytes on 64-bit ones, and the number of data breakpoints allowed depends on the system.

In our scenario with the m_Alive variable, in order to set a data breakpoint we would first put a regular breakpoint in the constructor of the game object. Once that is hit, we could then select from the menu Debug, New Breakpoint, New Data Breakpoint. For the address, we would type in&m_Alive, which is the address of m_Alive in memory. Then whenever that value changes in memory, the debugger will break, which will help us find the bad code that’s modifying it.

Source Control

Imagine you are working by yourself on a simple spaceship combat game that has code spread out across ten files. There’s a bug that if you pick up certain power-ups in a particular order, the ship stops being able to fire its weapons. You spend a couple hours debugging the problem (using a debugger, of course), and find the root cause in the code. You fix the bug and are happy that you solved the problem. You then move on to many other features and continue to change the files countless times.

A week later, suddenly, the bug appears again. You’re perplexed. Was it that you never fixed the bug in the first place, or that one of the numerous other changes you’ve made since then broke the code again? Well, you have a problem remembering what you changed to fix the bug in the first place, because you have absolutely no history of when the files were changed and for what reason. Even if you kept your computer on and IDE open for the entire week, it’s unlikely you can undo all the way back to the point where you fixed the bug. And anyways, you don’t want to lose one week of all the other changes.

Source control provides a clear history of every single file and when it was changed. It shows all previous versions of the file that were submitted, and used properly it provides detailed descriptions of why the lines of codes were changed in each version. With source control, it would be relatively easy to go through the change history and find out the specific fix that was implemented, which will provide much insight as to why the game broke again.

Although extremely useful for even a solo developer, source control is almost mandatory for a team project. With source control, you can have a centralized location that has the latest version of the code. Team members can make sure they are always synchronized, and there is a very clear history of who changed what files and why. It also allows you to manage scenarios where multiple programmers change the same file, which is pretty much a nightmare without source control.

Numerous different types of source control systems are available, but once you become familiar with one system, it typically is not that difficult to use another. There are always going to be differences in the workflow, but the basic premise is the same. This section covers two of the most popular systems: SVN and Git. The discussion of Git is more detailed, simply because that’s what’s used for the code samples in Chapters 13 and 14. But that doesn’t mean Git is the best solution in every scenario.

SVN

Subversion, or SVN, is a centralized source control system. In SVN, you first “check out” a copy of the code from the central server (called a repository). When you make any changes that you want to submit back to the server, you “commit” them. All operations, including checking out, committing, and updating to the latest version, must go through the central repository. The advantage of this is that the central server will always have the latest version, but the disadvantage is that operations can be slower because they always have to travel over the network.

Because SVN requires a centralized server, you have to set one up to use this source control system. You have a few potential options for servers. If the project in question is open source (meaning anyone can freely access the code), several different websites will host the SVN server for you. Some popular services include Google Code (http://code.google.com) and SourceForge (http://sourceforge.net).

If you don’t want to share your project’s source code with the world, you’ll need to have a private SVN server. On Windows, the easiest way to set up an SVN server is to use VisualSVN Server (http://www.visualsvn.com/server/). Alternatively, if you have a web host that provides shell access, you may be able to set up an SVN server through that, although it’s definitely harder to do so.

Once you have an SVN server, you need a way to connect to it. Although a command-line SVN client is available, the easiest client to use for Windows is TortoiseSVN (http://tortoisesvn.net/). What’s nice about TortoiseSVN is it integrates with the explorer, so you can right-click files and folders in order to perform operations on them.

The typical configuration of an SVN repository is to have three directories off of the root one: trunk, branches, and tags. The trunk is the current latest version, and is where most of the active development occurs. A branch is a separate copy of the code that might be used for working on a major new feature. The idea behind a branch is that the programmer tasked with the major feature can incrementally commit changes to the server without breaking the trunk for everyone. When the major new feature is tested sufficiently on the branch, it can then be merged back into the trunk. Finally, a tag is a copy of a specific version of the trunk. So, for example, if you release v1.0 of a program, you would want to make a v1.0 tag before continuing development. That way, the specific code for v1.0 is easily accessible, even after there has been many more changes to the trunk.

Git

Unlike SVN, Git is a distributed source control system. The distributed nature of it manifests in two main ways. First of all, you don’t need to have a central server to use Git. When you install a Git client, you can create and use source control locally. This means that if you’re working by yourself, operations can be faster than on a centralized source control system like SVN because you aren’t ever connecting to a server over the network.

However, if a project has more than one developer, it’s likely it still will need to have a central repository. This is where the other main difference between Git and SVN becomes apparent. Rather than just checking out the latest copy of the code from the central repository, in Git you clone the entire repository. This means you get not only the latest code, but a copy of the entire version history of every file since the beginning of the repository. This is extremely useful for open source projects that tend to fork, or have slightly different versions branch off from each other. One such project that forks with a great deal of frequency is Linux, so it’s not surprising that the main developer of Linux, Linus Torvalds, is also the main developer of Git.

Even if you don’t plan on forking a project, a clone of the repository still has the advantage that any operations you perform will actually be on your local clone, not on the central repository. This means that actions such as committing files will be faster, and you don’t necessarily have to be connected to a network to commit. But it also means that when you commit file changes, they will not automatically commit to the central server. To solve this, Git does provide functionality to “push” your commits from your local clone to the central repository and likewise “pull” changes from the central repository to the local one.

But if all you really want is just a central repository on a local network (as might happen in a professional environment), there may not really be much advantage to using Git. It has the additional overhead of needing to synchronize with the server, which if you’re going to want to do anyway, you might as well use a system that’s designed for that purpose.

Using Git

The source code for the games implemented in Chapters 13 and 14 is hosted on GitHub (http://github.com), which is the most popular site for hosting open source Git projects. Although you can use several different clients for Git, the easiest way to clone repositories from GitHub is to use the GitHub client. There are both Windows and Mac versions of the client, which both work roughly in the same way.

First, in order to use the GitHub client, you will need to create a GitHub account. This can just be done on the home page of their website. Once you have created an account, you can install the GitHub client for either Windows (http://windows.github.com) or Mac (http://mac.github.com). When you fire up the client for the first time, it’ll ask you for the account user name and password you just created.

Once the client is set up, you’re ready to use GitHub. Go to the web page for a GitHub repository you want to clone (for example, https://github.com/gamealgorithms/defense for the tower defense game in Chapter 14). Once you’re on the page, on the right side you should see a button labeled either “Clone in Desktop” or “Clone in Mac.” If you click that button, the Git clone procedure will kick off in the GitHub client. If this procedure worked properly, the project should be added to your list of repositories in the client.

Once a repository is cloned, you can right-click it in the client and select either Open in Explorer or Open in Finder, which will open the directory in which all the files are stored. You can then open the solution file and edit any files like you normally would.

When you’re ready to commit a change to your local repository, you can double-click the repository name in the GitHub client to open a more detailed view. In this view, you will see a list of all the files that have been modified, and can also expand each entry to see what changes were specifically made to each file. Select all the files you want to commit and then type in a commit message. Your message should be as descriptive as possible, because if you type in a generic message, it makes it a lot harder to immediately determine what changed in a specific commit.

Once you click the commit button, all the selected files will then be committed to your local repository. However, if you then want to push those changes to the central repository (if you have permission to do so), you can use the sync button at the top of the window. This will automatically pull any updates first and then push your changes. Note that if you don’t have permission to push your changes to the repository, you will get an error message.

The GitHub client does allow you to do other things, too, such as creating a separate branch. That, combined with the other features, is likely enough for most normal usage. But in order to perform the more complex operations Git is capable of, you will have to use another client. One such option is Atlassian SourceTree (http://www.atlassian.com/software/sourcetree/), or you can also use Git on the command line.

Diff and Merging Tools

A diff tool allows you to inspect the differences between two (or more) files. Many source control clients have this functionality integrated in some way, such as in the commit window in the GitHub client. The traditional diff tool was a program on UNIX that would spit out a text file that documented the differences between two other text files. Thankfully, more modern tools will actually display differences side by side in a graphical interface. When used in conjunction with source control, a diff tool can show you what was specifically changed in a file. One such open source diff tool is TortoiseMerge, which is included as part of the aforementioned TortoiseSVN.

A more complex use of such tools is to actually merge changes between multiple versions. Suppose you are editing GameState.cs, and at the same time another developer is also editing the file. If you commit your change to the central repository first, when the other developer tries to commit he’ll find out his GameState.cs is out of date. The source control client will then require him to update the file. If you were editing different parts of the file, the client will usually be able to automatically merge the different versions.

But what happens if both you and the other developer edited the same exact line in GameState.cs? In this case, the source control tool has no way of knowing which lines it should use, and it will complain that there is a conflict. To resolve this conflict, you have to do a three-way merge. In a three-way merge, the typical setup has things split up into sections for “their” changes, “your” changes, and the final merged file. For each conflict area, you will have to select which lines of code to use in the merged file. It might be theirs, yours, or some combination of the two.

Issue Tracking

Issue tracking involves documenting and then tracking the resolution of issues that pop up during development. An issue tracker is also called a bug database, though issues do not necessarily have to directly correspond to bugs. Although I wouldn’t recommend issue tracking early on in development, at a certain point the majority of features of a particular game will be implemented. This causes the focus to shift from creating new code to fixing any bugs in the existing code. Once a game reaches this phase of development, issue tracking becomes almost mandatory.

The typical workflow with an issue tracker is that the QA team tests the game and generates a bug report for each unique bug they encounter. These bug reports are then forwarded to a producer or another lead developer, who then determines who would be the best person to fix each bug. This could be due to a variety of factors, including who wrote the code in the first place, as well as who has the biggest workload at that point in time. Each issue is also assigned a priority, with the highest priority being reserved for crashes and other showstoppers.

The convenient part of having an issue tracker is that each developer will have a list of tasks to go through. This gives each team member a clear direction as to what he or she should be working on, which is important. There’s also some satisfaction in closing out bugs efficiently, or eventually getting down to zero bugs. Every time a bug is fixed, it’s then forwarded to the QA team to verify the fix. Once the fix is verified, the issue can then be closed, though there’s always the possibility of reopening a bug if it comes back.

Issue tracking also provides a way to get metrics such as how many total bugs there are, how many crash bugs there are, which developers are fixing the most bugs, and so on. So not only does issue tracking provide a clear list of tasks on an individual-by-individual basis, it also allows for large-scale statistics of how the project is progressing. If it’s late in the project and there still are more bugs coming in than being fixed, it likely means the release date needs to be delayed.

Many open source hosting platforms also provide issue tracking, including GitHub. So if you encounter a bug in one of the code samples from Chapter 13 or 14, feel free to submit an issue there! But if you aren’t using an open source host, you can use one of several other standalone programs, including Bugzilla (www.bugzilla.org) and Trac (http://trac.edgewall.org).