Debugging Utilities

Richard Kettering edited this page Jul 25, 2016 · 5 revisions
Clone this wiki locally

As you write stuff for the game, things inevitably won't behave as intended, and you'll need to debug them. Rather than guessing what went wrong, we have some tools that can let you diagnose what's going wrong.

Whilst the game is running, if you press control-D, the game will freeze, and a debugging console will appear on the screen (visually, it's not much at the moment, just an insertion point which lets you type). This allows you to type any formula that's valid in FFL, and have the game output the results of that formula. You can use this to ask all sorts of things that aren't visually obvious, such as how many hitpoints something has, what something's exact x,y position is, you can check variables - and most importantly, if you have an object that uses variables to keep track of what it's doing, or what's attached to it, you can check what's stored in them. FFL is able, of course, to not just query values, but change them. You can use the debug console to set any value you want, or run any function you want, just like you would when writing an object.

When you type in a variable like hitpoints, it can't mean anything on its own; hitpoints are always "of a certain object". To know which object is being referred to, the debug console lets you click on an object, and select it; frogatto himself (or really, the player object) starts as being selected by default, and you can select anything else by clicking on it, just like you would in the editor.

If I wanted to know how many hitpoints frogatto had, I could open the debug console, and type hitpoints. If I wanted to set frogatto to having only one hitpoint, I could type set(hitpoints,1).

The debug console also has a few other helpful commands:

  • step - will advance the current object only one frame and keep all other objects unchanged.
  • next - will advance the game one frame.
  • prev - will reverse a next command. prev doesn't work that nicely yet, though and after using it your game probably won't be able to continue outside of debug mode.

There are a few functions, useable either inside an object, or on the debug console, that are helpful:

  • plot_x(val) and plot_y(val) draw a straight vertical line at the given location (or horizontal, respectively). Both this and debug_rect(x,y,w,h) display for only one frame; in an object, this means they're useful only for use in on_process, but on the debug console, they'll display continuously, because the game is "paused" in a single frame.
  • debug_rect(x,y,w,h) draws a rectangle at the target location. This is very helpful because a lot of things in game operate with rects, and you can't see what these rects actually correspond to (there's a function, for example, called solid(x,y,w,h) that tests if anything is solid inside it). We've found that guessing is actually incredibly error-prone; far, far more than we expected. If you just explicitly display the rect being tested, you don't have to guess.

There are a few related functions that are really only useful inside an object:

  • debug() takes a expression, and outputs what it evaluates to. Like anything typed on the debug console, it will display in a little box on the screen, and it will also output to the standard unix console, as is present on macs and linux. It's not very useful in frogatto's debug console, because from there, you can just type anything directly and it will automatically be output. Remember you can concatenate numbers with strings via 'string'+variable. Example: debug('frogatto has '+hitpoints+' HP.') might print "Frogatto has 4 HP."

  • dump() fundamentally attacks the same issue as debug(); that of outputting some value from your code to the console, but changes the evaluation rules. When you run debug(some value), it evaluates to a command. If you're actually using that value in your code, you're then forced to inconveniently have two copies of the same code; one in your actual code-thats-doing-something to evaluate to the intended value, and another right next to it to debug-output the intended value to the console. For example, in a list of commands, you might have [set(level.player.x, my_value), debug(my_value)]. Instead, dump allows you to very conveniently wrap the value you intend to output in a single function call, which will both evaluate to the actual value you need, and also output it to the console. I.e. [set(level.player.x, dump(my_value))].

    The most important thing about dump isn't the convenience of using it instead of having to add another command to a list of commands - the most important thing is being able to use it when there is no list of commands at all; when, instead, you have a series of nested function calls. It allows you to get debug output in situations where you would otherwise have to engage in extreme code transformations in order to restructure your code to allow inserting a call to debug(). I.e. decimal :: get_player_damage(get_player_armor(get_enemy_damage_amount(get_enemy_damage_type)))) would require you to break it apart to get at some value inside it. Thanks to the magic of dump, you can just surround any expression inside it with dump, and it'll output the value of whatever particular item you want to target. I.e. if you wanted the value of the enemy damage amount, you could do: decimal :: get_player_damage(get_player_armor(dump(get_enemy_damage_amount(get_enemy_damage_type))))).

    dump() also has a two parameter form; it splits what-gets-output-to-the-console from always having to be exactly what gets passed in. The parameters are dump(what_to_ouput, what_value_to_evaluate_to) - this is especially useful in situations where you need to get at the parameters of some nested function, but you can't make them into a list, because they're separate parameters inside a function call. I.e.should_change_behavior_mode(is_monster_aggroed(x,y))is problematic to get the values of x and y in via the one-parameterdumpfunction, because you can't just surround x and y; they're separated by being different parameters. By placing them inside a two-parameter dump call, you can get the values via:should_change_behavior_mode(dump([x,y,],is_monster_aggroed(x,y)))`

  • debug_console() will open the debug console when evaluated, with the object that called it selected by default. It's not very useful when you're already inside the debug console, for obvious reasons.

There are also a few helpful command-line options

  • --set-fps= - if you follow this with something less than 50, which is our maximum frame rate, the game will run in slow motion. E.g. 25 would be half-speed. Sometimes broken animations and such are really hard to see right at full speed.
  • --show-hitboxes - this surrounds everything with translucent boxes indicating solidity, named collision rects (like attack_area), and terrain solidity. Useful for seeing how these actually line up with an object's sprite.
  • --fps - shows the actual FPS of the game, and some other more detailed output, to help diagnose if something you wrote is too processor-intensive.
  • --profile - dumps a much more detailed recording of what was consuming what amount of processing time to the console. This is done when the game exits. You can do --profile=output_file to dump this to a file named output_file instead.
  • --width 1200 --height 900 - can be used to set the screen to any arbitrary size - this will zoom the active gameplay to keep the same amount of stuff on-screen, but this does not zoom the editor, which allows you to have a much bigger editor window for designing levels.