Shawn Hargreaves Blog
Games are full of constant values. How high can my character jump? How long does it take to reload my crossbow? How much damage can a cat inflict on a zombie? What is the balance between ambient and diffuse in my lighting rig? How long does my game take to fade out when I quit back to the main menu?
Some of these constants are crucial to gameplay. Others determine whether your graphics look good. But most importantly, finding the right values creates the feeling of polish and attention to detail that will delight gamers and make them want to give you money :-)
I think one of the most underappreciated areas of game development is making constants easy to tweak. Consider the hack we used to simulate lightning in MotoGP. This only worked because we had exactly the right values for the frequency and length of flashes, the brightness, the difference in intensity from near to far, etc. The same technique would look ridiculous if any of these were even slightly wrong. To find the right values, we needed a way to experiment with different numbers and immediately see the results. It would take far too long if we had to recompile, redeploy, and reload the game every time we wanted to tweak a constant!
Y'all remember the console in Quake, right? That's how id Software made their constants easy to tweak. But a console is kind of intrusive, covering up part of the game, and also a pain to use on an Xbox that has no keyboard.
For MotoGP, we implemented a remote console using telnet. The telnet protocol is easy to implement: just open a TCP socket on port 23, then send text back and forth. We programmed the game (running on an Xbox devkit) to accept console commands in this way, and could connect to it from PC with a standard telnet client.
We used a TweakVariable class to create tweakable settings. We would declare a number of constants at the top of each source file:
BoolTweakVariable ShowSun(true, "sun/show");
IntTweakVariable RayCount(42, "sun/rays");
These types had automatic conversion operators, so we could use a BoolTweakVariable any place a bool was expected, an IntTweakVariable in place of an integer, etc:
for (int i = 0; i < RayCount; i++)
These types had constructors that automatically registered them with the telnet console, so we could connect to a running game and change their values at any time:
> telnet mydevkit
This stuff was seriously useful. Within days of adding it, tweakvars were sprouting up all over the place. We used them to tweak graphical effects, physics, AI, cameras, sounds, rumble settings, UI screen layout, transition effects, controller input, gameplay rules, and a multitude of debug features. Some settings were tweaked by programmers, some by designers, and others by artists. By the time we shipped, we had amassed more than three thousand tweakable constants!
It got to the point where we were doing so much tweaking that Damyan decided to make a proper user interface for it. He wrote a Python program that used the existing telnet protocol to communicate with the devkit, but replaced the text commands with buttons and sliders. Because our tweaking console was separate from the game, we didn't have to alter any game code to enable this new GUI, and could use a language (Python) that was not available on Xbox.
If you are a Game Studio developer, telnet is obviously not an option. But there are several alternatives:
Thankfully, Xbox 360 supports USB keyboards and chatpads, so the Quake-style console is also an option. I happen to be using such a console in my XNA games. :) Works great.
Your tweakable constructor takes a default value. I'd assume that you had a way to save changes to a data file or see the values that differ from the default. It looks like it might be a pain in the butt to keep the source code in sync with the latest tweaks. Can you talk about that a bit?
> I'd assume that you had a way to save changes to a data file or see the values that differ from the default. It looks like it might be a pain in the butt to keep the source code in sync with the latest tweaks.
Yes, that was pretty much a pain :-) We had a command that saved only modified tweakable values out to a file, but it was a manual process to take the values form this file and insert them back into the original source code.
One of those things that's kind of annoying, but not annoying enough to be worth spending much time fixing. In practice we'd only bother moving the tweaks back from the file to the C++ source once we were entirely done tweaking an area of the game, so this didn't happen too often.
We later implemented a more automated mechanism for managing this kind of data, but that was long after MotoGP had shipped :-)
I blogged about this technique recently, since I've been using it in my game. It's really useful, so I'm glad to see you blogging about it.
My approach is pretty similar to yours, and tackles the problem of saving changes back out by altering the original source code in-place at runtime. I don't have a solution for tweaking on the 360, though, since I do my tuning on the PC and only use the 360 for testing.
For anyone looking to try out this technique in their XNA game, feel free to grab the source from my blog post:
That is pure awesome coolness.
Interesting post Shawn, I like the idea of using telnet to enable this stuff on the Xbox. I assume you can do it on regular Xboxes as well as the devkit ones?
Andy Bitterman has a series of blog entries along similar lines from a while back. Well worth a read in my opinion:
I implemented a tool to do all those tweaks. It connects to a modified program using network socket. I have tested it on PC and PS3. I have a demo here: http://inalogic.com/MainSite/component/content/article/6-dev-tools/29-devtool-demo
There is still the issue of feeding back the final tweaks back into the source code so they become the default values. There are some possible solutions for that but I haven't settle on one yet.
I wonder if I can implement it on XNA with network sockets.
I have been implementing something like this but with a different approach. I have a python console (using IronPython, which in turn uses reflection) running as a GameComponent, so I can get/set any fields at runtime. That, combined with some UI sliders and the ability to save/load them have given me a similar tool but it only runs under Windows (because of IronPython not running on the 360) If interested you can check http://juancampa.blogspot.com I will be publishing a more stable version soon
Wonderful article - eye-opening for me!
I've started implementing something like this using reflection (so the tweakables are just properties marked with a suitable custom attribute) - see here for a (very brief!) description:
Did you not have a usable remote debugger? Even something as crude as GDB lets you easily inspect and modify variables, mostly obviating the need to hand roll a command language and the time spent gluing it to every part of your program you might conceivably want to tweak.
Debuggers can generally only modify program state while the program is paused, which is much less useful for seeing the effect of realtime adjustments.
motogp is an awesome game to play and it always rocks from the previous versions