Debug Menu in Flixel

So for the first feature to review, let’s look at the last one I added, a debug menu.

debugMenu

The textbox on the left displays the details of the current enemy wave list. It’s only there for informational purposes. The one next to it is where the real business is. It can be used to edit and then reset the current wave list. Assuming my towers are already balanced (they’re not), I can use this to tweak the difficulty curve of each level while still in the game. This saves having to re-compile every time I need to update the enemy list for a level.

The other things I can do from this level are remove all the towers from the current level and reset the funds available in the current level. Again, these is used to reset the level state for testing.

The other menus I’d like to add would let me change tower and enemy stats on the fly in order to tweak game balance without having to re-compile. Obviously this menu won’t be in the production release, but it will help immensely during development.

Now, on to the code. My flixel game state is set to a zoom level of 200%. This makes the sprites look a lot better, but text tends to look like crap. My solution is to code all the debug textboxes as regular AS3 textfields overlain on top of the actual game. Here’s the constructor on my DebugMenu class.

public class DebugMenu extends FlxGroup 
{
public var bgSprite:FlxSprite;
public var waveDisplay:TextField;
public var waveEntry:TextField;
public var moneyText:TextField;
public var resetTowerButton:AltButton; // Custom button class
public var resetWaveButton:AltButton;  // extending FlxSprite
public var setMoneyButton:AltButton;   //

public static var width:int = 400;
public static var height:int = 150;

public function DebugMenu() 
{
	bgSprite = new FlxSprite(0, 150, null);
	bgSprite.makeGraphic(DebugMenu.width, DebugMenu.height, 0x00cccccc);
	var gfx:Graphics = FlxG.flashGfx;
	gfx.clear();
	gfx.lineStyle(1, 0x000000, 1);			
	gfx.beginFill(0xffffff, 1);
	gfx.drawRoundRect(0, 0, DebugMenu.width - 1, DebugMenu.height - 1, 3);
	gfx.endFill();
	bgSprite.pixels.draw(FlxG.flashGfxSprite);
	bgSprite.dirty = true;
	bgSprite.exists = true;
	this.add(bgSprite);
	
	waveEntry = new TextField();
	waveEntry.x = 220;
	waveEntry.y = 310;
	waveEntry.width = 200;
	waveEntry.height = 200;
	waveEntry.setTextFormat(new TextFormat(null, 6, 0x000000));
	waveEntry.selectable = true;
	waveEntry.border = true;
	waveEntry.type = TextFieldType.INPUT;
	waveEntry.multiline = true;
	FlxG.stage.addChild(waveEntry);

        //same as above for waveDisplay and moneyText
	
	resetTowerButton = new AltButton(215, 185, 0xcccccc, 
              "Reset Towers", 50, 30, this.destroyAllTowers);
	resetTowerButton.label.color = 0x000000;
	this.add(resetTowerButton);
	
	resetWaveButton = new AltButton(215, 215, 0xcccccc,
              "Reset Waves", 50, 30, this.resetWaveList);
	resetWaveButton.label.color = 0x000000;
	this.add(resetWaveButton);
	
	setMoneyButton = new AltButton(265, 155, 0xcccccc,
              "Set Funds", 50, 30, this.setFunds);
	setMoneyButton.label.color = 0x000000;
	this.add(setMoneyButton);
}

As you can see, all the Flixel elements are added directly to DebugMenu, an extension of FlxGroup. This makes it really easy to show or hide them. On the textfields, making them selectable allows copy/paste operations. The type has to be set to TextFieldType.INPUT in order to alter the contents.

Since I’m using a mix of Flixel objects and AS3 objects, I needed to write an additional function to show/hide the debug menu. If I was just using Flixel objects, setting Registry.debugMenu.exists = false; would hide all the elements. Here’s that code:

public function showHide(makeVisible:Boolean):void {
	if (makeVisible) {
		this.exists = true;
		if(!this.waveEntry.stage) {
			FlxG.stage.addChild(this.waveEntry);
		}
		if(!this.waveDisplay.stage) {
			FlxG.stage.addChild(this.waveDisplay);
		}
		if(!this.moneyText.stage) {
			FlxG.stage.addChild(this.moneyText);
		}
	}
	else {
		this.exists = false;
		if(this.waveEntry.stage) {
			FlxG.stage.removeChild(this.waveEntry);
		}
		if(this.waveDisplay.stage) {
			FlxG.stage.removeChild(this.waveDisplay);
		}
		if(this.moneyText.stage) {
			FlxG.stage.removeChild(this.moneyText);
		}
		FlxG.stage.focus = null;
	}
}

Above is a technique I learned recently that I wish I had known when I was coding this whole thing from scratch in AS3. On a textfield, .stage will be null if the object has not been placed and non-null otherwise. This is the most surefire way to make sure you’re not using .removeChild on an object that hasn’t been placed and that you’re not using .addChild on an object that’s already on the stage.

One last interesting piece of code to deal with textfields in AS3. I thought I could just use .text.split(“n”) to divide the text into an array of lines. Unfortunately, .text removes the new line characters, but using .getRawText().split(“n”); gives you the behavior you want in order to split up the text by line.

In order to get all this stuff working the way I wanted, I needed to rewrite my PlayState class in order to update certain parts of the game when the debug menu is visible, other parts when the game is paused and basically everything when the game is running. I might have shown how I did the last two parts before, but here’s what my PlayState.update() function looks like now.

override public function update():void
{
	//NUMPADPERIOD is used to show and hide the debug menu
	if (FlxG.keys.justPressed("NUMPADPERIOD")) {
		trace("Toggling Debug Menu");
		Registry.debugMenu.showHide(!Registry.debugMenu.exists);
		this.debugActive = !this.debugActive;
	}

	//Freezes everything once the level is over
	if (gameIsOver) {
		if (FlxG.keys.justPressed("ENTER") ||
				FlxG.keys.justPressed("P")) {
			FlxG.switchState(new MenuState());
		}
	}
	//Only update the debug menu when it's visible
	else if (this.debugActive) {
		Registry.debugMenu.update();
	}
	//Update the game otherwise
	else {
		//PlayState.update gets run at 30FPS
		//Scaling gameSpeed allows the game to run
		//At 2x/4x speed in order to fast forward the game
		//This is a common tower defense device
		for (var i:int = 0; i < gameSpeed; i++) {
			super.update();
		}
		
		//Runs things like checking for mouse clicks,
		//button presses and allowing tower placement
		//while the game is paused
		this.runNonGameUpdates();
	}
}

So that’s pretty much it for my debug menu. If you’re curious how the AltButton class is set up, I can answer questions in the comments or do a separate post.

  1. No comments yet.
  1. No trackbacks yet.