Advertisement

How to Make UI Components for FlashPunk Games

by

It's not easy to create the UI side of your game with FlashPunk, as it doesn't include any UI components like buttons or text fields by default. This isn't a restriction, though; it just means you're completely free to create the UI exactly as you like. But you need to know how to do it first! This tutorial will teach you how to build some custom UI components, and show you the different ways you can customize them to exactly fit the style of your game.


Final Result Preview

Let's take a look at the final result we will be working towards:


Step 1: Introduction

Many FlashPunk users tend to have problems with the UI side of their games. There's no 'easy' way of making buttons and other interactible-elements like text fields or checkboxes, for example, in FlashPunk. No Button class.

That may seem of a restriction, yes, and many newcomers find it a little bit confusing at first... but there's a reason behind that. Games, each one of them, tend to have a custom user interface unique to themselves, which design is related to the mechanics or theme of the game itself. You won't see any good game (generally speaking) that has the default window buttons for its controls!

We could argue that, in this case, there could be some classes providen with the bare-bones of the functionality, the minimum required for some buttons to work, and leave the graphic side to the user... yes, that is true... but the generalization we would have to use in those classes would be either too big and confusing or too specific and not costumizable enough. (believe me, I was in charge of the semi-failed semi-working Punk.UI project.) We will learn how to code our own components instead!

So what this tutorial is going to show you is how to build your own UI elements for your game in FlashPunk, give them behaviour, and show some tricks to make their graphic part with the most used techniques... but remember that each of your games will need a different UI graphically-wise!

As the tutorial is really lengthy, it's been divided into multiple parts. This first part will teach you how to build each component, without the eye-candy and costumization. We will introduce these later.

To code the example SWF with our UI, we are going to use FlashPunk, as this is a FlashPunk tutorial, and FlashDevelop for the IDE. If you feel more comfortable with another IDE, like FlashBuilder, you are free to use that, just adapt the specific parts of this tutorial.

Previous knowledge on FlashPunk is needed to follow this tutorial. This is not a general FlashPunk tutorial, this is a UI tutorial using FlashPunk. If you don't know FlashPunk, you can take a look at the Introductory FlashPunk tutorials on Activetuts+ and also check out the official FlashPunk tutorials.


Step 2: Create a New Project

Open FlashDevelop and click Project > New Project to open the New Project window.

FlashDevelop New Project Window

Choose ActionScript 3 > AS3 Project. For the name of the project, put in "FlashPunk UI". For the location, click and navigate to the folder you would like to save it into. Leave the "create directory for project" checkbox selected and click OK.


Step 3: Download the Latest FlashPunk

The Downloads page of FlashPunk

Go to the official FlashPunk webpage, FlashPunk.net, and click the Download button on the navigation bar. Then click the "Download FlashPunk" link on the top of the page. It should take you to the GitHub download webpage. Click the "Download .zip" button (or "Download .tar.gz" if you prefer that format) and save the file somewhere you know.


Step 4: Add FlashPunk to Our Project

Added FlashPunk to our project

Now that we have downloaded FlashPunk, we have to add it to our project. To do so, we will simply copy the "net" folder from the FlashPunk zip to our "src" folder in our project, like usual.


Step 5: Starting the Engine

We have to initialize FlashPunk in our document class now, so it takes control of our game, like on every FlashPunk project. We do so by making our document class extend the Engine class, and calling super with the required parameters. We will give our application a size of 550x400 pixels. Don't forget to right-click on our project, go to Properties and configure the dimensions to 550x400 pixels as well.

package 
{
	import net.flashpunk.Engine;
	
	public class Main extends Engine
	{
		public function Main():void 
		{
			super(550, 400);
		}
	}
}
Setting our project's dimensions

Step 6: Creating Our World

Now, we have to create a FlashPunk World to hold our entities. We will place every UI component we create there, so we can test them. In a real-world case, each of our Menu States would be a different World, as well as the Game itself. So we will create a new class which will extend World, called TestWorld, in our "src" folder.

package  
{
	import net.flashpunk.World;
	
	public class TestWorld extends World 
	{
		
		public function TestWorld() 
		{
			super();
		}
		
	}
}

Then we'll tell our Engine class to go to our TestWorld on startup by overriding the init function. Don't forget to import the class FP from net.flashpunk!

		override public function init():void 
		{
			super.init();
			
			FP.world = new TestWorld;
		}

Step 7: The Button Entity

The first thing we are going to build is our Button component. Every component we're going to make will be an Entity, as it's the most logical step for making something in FlashPunk which will live in a World.

First of all, we will create a new folder inside the "src" folder, to keep things a bit organized. We will name this folder "ui", and will hold all of our components.

Then we create a class there named Button, which will extend Entity. The package will be ui, as it's inside the ui folder.

package ui 
{
	import net.flashpunk.Entity;
	
	public class Button extends Entity 
	{
		public function Button(x:Number=0, y:Number=0) 
		{
			super(x, y);
		}
	}
}

Now we will add a new instance of this Button class in our World, so we can see it working... when we finish it, as at the moment it's an invisible Entity. So, add this in our TestWorld class, and don't forget to import the Button class using import ui.Button;

		override public function begin():void 
		{
			super.begin();
			
			add(new Button(10, 10));
		}

Step 8: Graphic for Our Button

To make our button visible, it will need a graphic. I have created an image for our Button, which you can use freely, but you can also create your own graphic if you want:

Button graphic

Save this as "button.png" without the commas in a new folder named "gfx", situated inside an assets folder which will be in the root of your project (at the same level of your src folder).

Now we need to insert this button graphic in our game. To do so, we will embed the image and then tell FlashPunk to use it as our graphic. To keep things a bit organized, we will embed everything we need in a new class called Assets. This is what I tend to do on all my projects, and it works like a charm! So we will proceed to create a new Assets class, and embed the graphic as a public static constant, so we can access it from outside:

package  
{
	public class Assets 
	{
		[Embed(source = "../assets/gfx/button.png")] public static const BUTTON:Class;
	}
}

Finally, we will tell FlashPunk to use this as the graphic of our Button. We can use it as an Image or as a Stamp. The difference is, Image will consume more memory but will allow us to transform the graphic, and the Stamp will consume less memory but won't allow any transformation at all, unless they are applied manually directly to the BitmapData. We will currently use a Stamp, as we don't need to transform the button yet. Add this to our Button class, and don't forget to import Stamp.

		public function Button(x:Number=0, y:Number=0) 
		{
			super(x, y);
			
			graphic = new Stamp(Assets.BUTTON);
		}

If you test the project now, you will see our graphic on the World, but clicking on it won't do anything. We will add some behaviour now!


Step 9: Adding Interactivity

To make an Entity respond to mouse clicks on FlashPunk, we just need to: check the mouse is over the entity and check if the mouse was released this frame. The second step is really easy, we just have to check the value of the mouseReleased variable in the Input class, but for the other, we must do a collision test between a point (mouse coordinates) and the entity, and to do so, we will need to define the entity collision. At the moment we will use a hitbox, as it's the simplest option.

So here's the code achieving all of what we just said, with an explanation below:

package ui 
{
	import net.flashpunk.Entity;
	import net.flashpunk.FP;
	import net.flashpunk.graphics.Stamp;
	import net.flashpunk.utils.Input;
	
	public class Button extends Entity 
	{
		public function Button(x:Number=0, y:Number=0) 
		{
			super(x, y);
			
			graphic = new Stamp(Assets.BUTTON);
			setHitboxTo(graphic);
		}
		
		override public function update():void 
		{
			super.update();
			
			if (collidePoint(x, y, world.mouseX, world.mouseY))
			{
				if (Input.mouseReleased) click();
			}
		}
		
		protected function click():void
		{
			FP.screen.color = Math.random() * 0xFFFFFF;
			trace("click!");
		}
	}
}
  • Line 15: We set the Entity's hitbox to our graphic, so it will have the same Width and Height of the Stamp.
  • Line 22: We check if the mouse position in the world we are collides with the entity.
  • Line 24: If the mouse was released this frame, we call the click function.
  • Lines 28-32: This code will be executed when the button is clicked. It will trace a click message and change the background colour, so we notice the button has been pressed.

Some of you may notice this class would perform better if we checked the mouse state first and then check the collision, but as we will have to add the hover graphics, we would have to change it back to this, so we'll leave it this way.

If you test the game now, it should look like this, but with your button graphic:


Step 10: Hover and Down Graphics

Right now, our Button is really boring, and it does not show ANY sign of interactivity, apart from its callback. It's the time to add some graphics to show the different states: normal, hover, and down.

First of all, we need to make different graphics for each, so let's get working! You can grab these two I made or simply make your own. Put them in the gfx folder with the names button_hover.png and button_down.png.

Button Hover Graphic
button_hover.png
Button Hover Graphic
button_down.png

Then, we will add them in our Assets class.

		[Embed(source = "../assets/gfx/button.png")] public static const BUTTON:Class;
		[Embed(source = "../assets/gfx/button_down.png")] public static const BUTTON_DOWN:Class;
		[Embed(source="../assets/gfx/button_hover.png")]  public static const BUTTON_HOVER:Class;

Now we have to hold those graphics somewhere in our button, and switch to the correct one when it's needed. So we will create three variables to hold the normal, the down, and the hover graphics respectively, and switch the graphic properties to each of the three as appropriate.

		protected var normal:Graphic;
		protected var hover:Graphic;
		protected var down:Graphic;
		
		public function Button(x:Number=0, y:Number=0) 
		{
			super(x, y);
			
			normal = new Stamp(Assets.BUTTON);
			hover = new Stamp(Assets.BUTTON_HOVER);
			down = new Stamp(Assets.BUTTON_DOWN);
			
			graphic = normal;
			
			setHitboxTo(graphic);
		}

Step 11: Fix a Little Bug

Before making the graphic behaviour, we want do something else. We need to keep track of some data which will also solve a little issue: if I press the mouse outside the button, then go to the button and release the mouse, it will detect as a mouse click. We don't want that. So we will create a new protected boolean called clicked, which will simply tell us if the mouse was first pressed over the button or not.

		protected var normal:Graphic;
		protected var hover:Graphic;
		protected var down:Graphic;
		
		protected var clicked:Boolean = false;

Then we will apply the following changes on the update function: first, inside the collision check, if the mouse was pressed this frame (Input.mousePressed) we will set the clicked boolean to true. Then, on the check for the released mouse button, we will also check for the clicked variable, so we will only detect a button click if the mouse was pressed while over it before. Finally, outside of the collision check, we set clicked to false when the mouse is released.

override public function update():void 
{
	super.update();
	
	if (collidePoint(x, y, world.mouseX, world.mouseY))
	{
		if (Input.mousePressed) clicked = true;
		
		if (clicked && Input.mouseReleased) click();
	}
			
	if (Input.mouseReleased) clicked = false;
}

Step 12: Assigning the Hover and Down Graphics

For the graphics itself, we must first plan the behavior we have in mind. When the mouse is over the graphic and it's not pressed, we will display the hover state. If the mouse is over the graphic and it's pressed, we will display the pressed state. If the user presses the button and, while keeping the mouse button down, moves the mouse out of the button collision, we will display the hover state, imitating AS3's SimpleButton behavior. In all the other cases, we will display the normal state.

		override public function update():void 
		{
			super.update();
			
			if (collidePoint(x, y, world.mouseX, world.mouseY))
			{	
				if (Input.mousePressed) clicked = true;
				
				if (clicked) graphic = down;
				else graphic = hover;
				
				if (clicked && Input.mouseReleased) click();
			}
			else
			{
				if (clicked) graphic = hover;
				else graphic = normal;
			}
			
			if (Input.mouseReleased) clicked = false;
		}

Inside the button collision check we also check if the mouse button is being pressed at the moment. We will check that using the clicked variable, and not the Input.mouseDown variable, because we want to be sure we show the pressed graphic the mouse has been clicked while over the button, not when clicked outside of the button and then dragged on top of it. In case the mouse is being pressed, we will display the down state, if not, the hover state, because the mouse will be over the button but it won't be pressed.

In the other pair of checks, when the mouse isn't over the button, we check first if the mouse has been clicked. If it has, we will display the hover state as we said before. If not, the normal state will be shown.

Here you can see how it should look at the moment, but with your own graphics instead:


Step 13: Checkboxes

If you think about it, a checkbox isn't really that different from a button. The only difference, apart from the graphic, is they also have a state which determines whether they are checked or not. In fact, a checkbox and a push button - which is a button that remains pressed until you click on it again - are the same.

For that reason, creating a checkbox is really easy if we know how to create a button. All we need to add is an extra variable that is toggled on each click, and with a few extra checks on the graphic changes, where we determine which version of the graphic to show: the checked or the unchecked one.

I would also like to teach you something extra as we do the checkbox. For the button, we created the graphics in different files, but what if we want all of the states in the same graphic? That's pretty simple, we just need to use the clipRect property of the Image class, as we will show when assigning our checkbox graphics.

So, as checkboxes and buttons share so many features, it's logical that we want our checkbox class to extend the button class. But, if we did that with the current code, we would have to replace the full update function to considerate our checkbox checked state on the graphics changes. Thus, before creating our Checkbox class, we will refactor our Button class a bit before, so things are easier and cleaner for us.


Step 14: Refactoring Our Button

To be able to insert the checked consideration when our graphics change, all we need to do is abstract the graphic change. So, instead of setting the graphic directly in the update function, we will call another function telling what state we want, and that function will do the actual graphic switch.

The function we will create will be called changeState, and will accept one argument as an integer, the state. 0 will mean normal, 1 will mean hover and 2 will mean down. As we could get a bit confused and forget the meaning of those numbers easily (well, not in THIS case, but this technique may be useful to you in other, more complicated cases), we will create some constants that will hold this values instead.

First, we create those constants on our Button class:

		protected const NORMAL:int = 0;
		protected const HOVER:int = 1;
		protected const DOWN:int = 2;

Then we substitute all the graphic changes to call our yet-to-be-created function:

		override public function update():void 
		{
			super.update();
			
			if (collidePoint(x, y, world.mouseX, world.mouseY))
			{	
				if (Input.mousePressed) clicked = true;
				
				if (clicked) changeState(DOWN);
				else changeState(HOVER);
				
				if (clicked && Input.mouseReleased) click();
			}
			else
			{
				if (clicked) changeState(HOVER);
				else changeState(NORMAL);
			}
			
			if (Input.mouseReleased) clicked = false;
		}

Finally we create the function, using a switch statement with three cases:

		protected function changeState(state:int = 0):void
		{
			switch(state)
			{
				case NORMAL:
					graphic = normal;
					break;
				case HOVER:
					graphic = hover;
					break;
				case DOWN:
					graphic = down;
					break;
			}
		}

Now we're ready to easily code our checkbox! But first...


Step 15: Checkbox Graphics

As we explained before, we will place all the checkbox graphics in a single file, so you can see both techniques (separate files or same files) and choose whichever you prefer.

The order of the file will be as follows: normal - hover - down; the top row will be the unchecked states and the bottom one will be the checked states. You can use your own graphics or download this one we provide. Don't forget to save the graphic as checkbox.png inside the gfx folder.

Checkbox Graphic

Now we'll add it to our Assets class so we can use it.

		[Embed(source = "../assets/gfx/button.png")] public static const BUTTON:Class;
		[Embed(source = "../assets/gfx/button_down.png")] public static const BUTTON_DOWN:Class;
		[Embed(source = "../assets/gfx/button_hover.png")]  public static const BUTTON_HOVER:Class;
		[Embed(source = "../assets/gfx/checkbox.png")] public static const CHECKBOX:Class;

Finally, we will create our Checkbox class and setup the variables containing the graphics. So, first we will create the Checkbox class in the ui folder, and make it extend our Button.

package ui 
{
	public class Checkbox extends Button 
	{
		public function Checkbox(x:Number=0, y:Number=0) 
		{
			super(x, y);
			
		}
	}
}

Then, we will add three more variables containing the checked graphics. The unchecked graphics will be at the normal, hover and down variables we already have.

		protected var normalChecked:Graphic;
		protected var hoverChecked:Graphic;
		protected var downChecked:Graphic;
		
		public function Checkbox(x:Number=0, y:Number=0) 
		{
			super(x, y);
		}

And now, we will populate them, as Images so we can clip them using the clipRect variable. This variable accepts a flash Rectangle, which is a Class with x, y, width and height properties. So, when Image sees we provide a clipRect, it will crop itself using that information. That's how it will look like in my case. You might have to adapt the values, so they fit your own graphics dimensions:

		public function Checkbox(x:Number=0, y:Number=0) 
		{
			super(x, y);
			
			normal = new Image(Assets.CHECKBOX, new Rectangle(0, 0, 38, 34));
			hover = new Image(Assets.CHECKBOX, new Rectangle(38, 0, 38, 34));
			down = new Image(Assets.CHECKBOX, new Rectangle(76, 0, 38, 34));
			
			normalChecked = new Image(Assets.CHECKBOX, new Rectangle(0, 34, 38, 34));
			hoverChecked = new Image(Assets.CHECKBOX, new Rectangle(38, 34, 38, 34));
			downChecked = new Image(Assets.CHECKBOX, new Rectangle(76, 34, 38, 34));
			
			graphic = normal;
			setHitboxTo(normal);
		}

If you take a look at the code, you can see that in the end we are also assigning our graphic to the normal state, and resizing our hitbox to match the Checkbox graphics.


Step 16: Adding Our Checkbox

Now we will add the checkbox into our World and see how it looks! Add this in our TestWorld class:

		override public function begin():void 
		{
			super.begin();
			
			add(new Button(10, 10));
			add(new Checkbox(20, 140));
		}

Now test the project... hey, wait! Our checkbox acts just like a normal button, it doesn't check and uncheck itself! We haven't added the behavior yet, that's what we're going to do now.


Step 17: Checking and Unchecking the Checkbox

First of all, we need to create a public boolean which will hold the checked state of our Button. It will be called... oh, surprise! checked. So...

		protected var normalChecked:Graphic;
		protected var hoverChecked:Graphic;
		protected var downChecked:Graphic;
		
		public var checked:Boolean = false;

Now, we will need to toggle this variable each time we click the checkbox. The easiest way to do so is to override the click function, and toggle it, so add this in our Checkbox:

		override protected function click():void 
		{
			checked = !checked;
			
			super.click();
		}

But, if we test the project, the checkbox won't change yet. We need to make one last change: make a check when setting each graphic to change it to the checked or unchecked version. To do that, we just need to override the changeState function we refactored before:

		override protected function changeState(state:int = 0):void 
		{
			if (checked)
			{
				switch(state)
				{
					case NORMAL:
						graphic = normalChecked;
						break;
					case HOVER:
						graphic = hoverChecked;
						break;
					case DOWN:
						graphic = downChecked;
						break;
				}
			}
			else
			{
				super.changeState(state);
			}
		}

So first we check the checked property. If it's true, we check the state we just changed to, and set it to our corresponding checked graphic. Otherwise, if the checkbox is not checked, we call our Button version of the changeState, which simply sets the graphic to the unchecked state. By using the super there, we need to write less code to do the same behavior! Yay!

If we test the project, we can see the checkbox working now. It should look like this, but with your own graphics:


Step 18: Radio Buttons

If we think about it, a radio button is practically the same as a Checkbox. The only difference is, while the Checkbox is totally independent from the other checkboxes, a RadioButton is part of a group, and in that group, only ONE member can be checked at the same time, so we will have to handle that.

So, instead of making the RadioButton open and close itself, it will tell the group it needs to be checked, and the group will uncheck all of the other radio buttons, and check ours... and what's the group?

Basically, the RadioButton Group will be a special class with an Array (well, in our case an AS3 Vector) containing all the RadioButtons belonging to the group. It will also contain methods to add and remove buttons from the group.

First of all, though, we will make the graphics for our RadioButtons...


Step 19: RadioButton Graphics

We will make the graphics for the RadioButton the same way we did it for the Checkbox. You can make your own costumized graphic if you want to, but if not, you can use this. Save your graphic as "radiobutton.png" in the gfx folder.

RadioButton Graphic

Now we'll add it to our Assets class so we can use it.

		[Embed(source = "../assets/gfx/button.png")] public static const BUTTON:Class;
		[Embed(source = "../assets/gfx/button_down.png")] public static const BUTTON_DOWN:Class;
		[Embed(source = "../assets/gfx/button_hover.png")]  public static const BUTTON_HOVER:Class;
		[Embed(source = "../assets/gfx/checkbox.png")] public static const CHECKBOX:Class;
		[Embed(source = "../assets/gfx/radiobutton.png")] public static const RADIOBUTTON:Class;

After that, we create our RadioButton class which will extend RadioButton, and we'll set up the graphics the same way we did it before.

package ui 
{
	import flash.geom.Rectangle;
	import net.flashpunk.graphics.Image;
	
	public class RadioButton extends Checkbox 
	{
		public function RadioButton(x:Number=0, y:Number=0) 
		{
			super(x, y);
			
			normal = new Image(Assets.RADIOBUTTON, new Rectangle(0, 0, 39, 44));
			hover = new Image(Assets.RADIOBUTTON, new Rectangle(39, 0, 39, 44));
			down = new Image(Assets.RADIOBUTTON, new Rectangle(78, 0, 39, 44));
			
			normalChecked = new Image(Assets.RADIOBUTTON, new Rectangle(0, 44, 39, 44));
			hoverChecked = new Image(Assets.RADIOBUTTON, new Rectangle(39, 44, 39, 44));
			downChecked = new Image(Assets.RADIOBUTTON, new Rectangle(78, 44, 39, 44));
			
			graphic = normal;
			setHitboxTo(normal);
		}
	}
}

Step 20: RadioButton Group

It's time to make the radiobutton group itself now. The class will hold all the radiobuttons in a Vector of RadioButtons. It will also have the following methods: add(), for adding a new radio button, addList(), to add multiple radio buttons in a single step, remove(), to remove a radio button, removeList(), the equivalent of addList() but for removing, and removeAll(), to remove all the radio buttons from the group at once. It will also have a getAt() method, to get a radio button by the index, and a getIndex(), to get the index of a button. We won't spend much time explaining those methods, as they are basic operations for arrays.

Then, when creating a RadioButton, it will ask for a radio button group as the parameter, and it will be added automatically there if provided. Also, when removing the RadioButton from the world, it will also be removed from its group. Finally, when clicked, it won't do anything by itself but call an internal method of the group, which will be called click(). This method will uncheck all the radio buttons and check the one which called the method.

First of all, we create the group. I have provided some comments to explain a few things, but as it's only basic operations with an array, I won't explain it all:

package ui 
{
	public class RadioButtonGroup 
	{
		public var buttons:Vector.<RadioButton> = new Vector.<RadioButton>;
		
		public function RadioButtonGroup(...buttons) 
		{
			//we add the buttons provided to the constructor, if any
			if (buttons) addList(buttons);
		}
		
		public function add(button:RadioButton):void
		{
			buttons.push(button);
		}
		
		public function addList(...buttons):void
		{
			if (!buttons) return;
			if (buttons[0] is Array || buttons[0] is Vector.<RadioButton>)
			{
				//if the parameter is an array or vector of radio buttons, we add the buttons in the vector / array
				for each(var b:RadioButton in buttons[0]) add(b);
			}
			else
			{
				//if the parameters are simply a comma separated list of buttons, we add those instead
				for each(var r:RadioButton in buttons) add(r);
			}
		}
		
		public function remove(button:RadioButton):void
		{
			buttons.splice(getIndex(button), 1);
		}
		
		public function removeList(...buttons):void
		{
			if (!buttons) return;
			if (buttons[0] is Array || buttons[0] is Vector.<RadioButton>)
			{
				//if the parameter is an array or vector of radio buttons, we remove the buttons in the vector / array
				for each(var b:RadioButton in buttons[0]) remove(b);
			}
			else
			{
				//if the parameters are simply a comma separated list of buttons, we remove those instead
				for each(var r:RadioButton in buttons) remove(r);
			}
		}
		
		public function removeAll():void
		{
			//fastest way to clear a vector
			buttons.length = 0;
		}
		
		public function getAt(index:int):RadioButton
		{
			return buttons[index];
		}
		
		public function getIndex(button:RadioButton):int
		{
			return buttons.indexOf(button);
		}
	}
}

Now, we will make our RadioButtons ask for a group on the constructor, and add themselves on it when provided:

		public function RadioButton(x:Number=0, y:Number=0, group:RadioButtonGroup = null) 
		{
			super(x, y);
			
			if (group) group.add(this);
			
			normal = new Image(Assets.RADIOBUTTON, new Rectangle(0, 0, 39, 44));
			hover = new Image(Assets.RADIOBUTTON, new Rectangle(39, 0, 39, 44));
			down = new Image(Assets.RADIOBUTTON, new Rectangle(78, 0, 39, 44));
			
			normalChecked = new Image(Assets.RADIOBUTTON, new Rectangle(0, 44, 39, 44));
			hoverChecked = new Image(Assets.RADIOBUTTON, new Rectangle(39, 44, 39, 44));
			downChecked = new Image(Assets.RADIOBUTTON, new Rectangle(78, 44, 39, 44));
			
			graphic = normal;
			setHitboxTo(normal);
		}

Finally, we'll add the buttons on the world, so we can test them. We will add 3 buttons in a group, and 2 in a different one.

		override public function begin():void 
		{
			super.begin();
			
			add(new Button(10, 10));
			add(new Checkbox(20, 140));
			
			var group1:RadioButtonGroup = new RadioButtonGroup();
			add(new RadioButton(20, 200, group1));
			add(new RadioButton(20, 250, group1));
			add(new RadioButton(20, 300, group1));
			
			var group2:RadioButtonGroup = new RadioButtonGroup();
			add(new RadioButton(200, 200, group2));
			add(new RadioButton(200, 250, group2));
		}

If you test it now, the radio buttons will still behave as normal checkboxes. It's time to change that now.


Step 21: RadioButton Click Behavior

First of all, we need to override the click function of the RadioButton, and instead of calling the code there is on the checkbox using super, we will call the still-to-be-created click function of our RadioButton group... oops! How are we going to call this function, if we haven't got a reference to the group? A simple solution would be to set a reference from the group provided on the constructor but... then we won't be able to change groups, and all of the methods we can see in the group will be useless.

What we are going to do instead is, first add a variable on the RadioButton which will reference its group, and we will set this on the added method of the group. We will also set it to null when removing the button:

	public class RadioButton extends Checkbox 
	{
		internal var group:RadioButtonGroup;
		
		// [...] all the methods in the RadioButton class were omitted for brevety.
	}

Now we add this on the following methods of the RadioButton group:

		public function add(button:RadioButton):void
		{
			button.group = this;
			buttons.push(button);
		}
		public function remove(button:RadioButton):void
		{
			button.group = null;
			buttons.splice(getIndex(button), 1);
		}

And we will also set all the group references to null on the removeAll function:

		public function removeAll():void
		{
			for each(var b:RadioButton in buttons) b.group = null;
			//fastest way to clear a vector
			buttons.length = 0;
		}

Now we are ready to call the click function on the RadioButton!

		override protected function click():void 
		{
			group.click(this);
		}

And finally we will build the click function. This function will uncheck all the RadioButtons in the group, and then check the button provided.

		internal function click(target:RadioButton):void
		{
			for each(var b:RadioButton in buttons) b.checked = false;
			target.checked = true;
		}

Now, just before testing, we will also make our button remove itself from the group when removed from the world.

		override public function removed():void 
		{
			super.removed();
			
			group.remove(this);
		}

This is how it will look like, but with your own graphics!


Step 22: Custom Parameters: Text

What if we want our buttons to contain some text? For example, in the menu of our game, we may want to have a Play button, a Help button, etc. and we want those buttons to have some text on them, which tells which of them is the play and which of them is the help. Currently, the only way we can do it is create a new image for each of them, create a new class as well and change the image in the class...

So what we can do is accept an extra parameter in our components: the text. This will be a string we will send to our components, and then they will display it as text. Super easy!

Let's implement it in our Button now. First, we will add the parametrer text as a string, and also a label variable with type of Text, which is a FlashPunk graphic:

		protected var label:Text;
		
		public function Button(x:Number=0, y:Number=0, text:String = "") 
		{
			super(x, y);
			
			normal = new Stamp(Assets.BUTTON);
			hover = new Stamp(Assets.BUTTON_HOVER);
			down = new Stamp(Assets.BUTTON_DOWN);
			
			graphic = normal;
			
			setHitboxTo(graphic);
		}

Then we will instantiate this text, with a size of 16 pixels and black color, with the width of the button (minus the borders), wordWrap activated and aligned to the center.

		public function Button(x:Number=0, y:Number=0, text:String = "") 
		{
			super(x, y);
			
			var normalStamp:Stamp  = new Stamp(Assets.BUTTON);
			
			label = new Text(text, 10, 0, { size: 16, color: 0x000000, width: normalStamp.width - 30, wordWrap: true, align: "center" } );
			
			normal = normalStamp;
			hover = new Stamp(Assets.BUTTON_HOVER);
			down = new Stamp(Assets.BUTTON_DOWN);
			
			graphic = normal;
			
			setHitboxTo(normalStamp);
		}

We also created an extra variable, a Stamp, which will hold the stamp of the normal graphic, so we can reference its width. We set normal graphic to the normalStamp.

Now we need to display this text somewhere. To do this, we need to include it in the graphic of our button. In FlashPunk, to display more than a single graphic at once, we need to put all of them in a graphiclist, and use this as the graphic instead. So we will do something similar, to make all of our graphics contain the text:

		public function Button(x:Number=0, y:Number=0, text:String = "") 
		{
			super(x, y);
			
			var normalStamp:Stamp  = new Stamp(Assets.BUTTON);
			
			label = new Text(text, 10, 0, { size: 16, color: 0x000000, width: normalStamp.width - 30, wordWrap: true, align: "center" } );
			
			normal = new Graphiclist(normalStamp, label);
			hover = new Graphiclist(new Stamp(Assets.BUTTON_HOVER), label);
			down = new Graphiclist(new Stamp(Assets.BUTTON_DOWN), label);
			
			graphic = normal;
			
			setHitboxTo(normalStamp);
		}

Finally, we will center our text vertically:

		public function Button(x:Number=0, y:Number=0, text:String = "") 
		{
			super(x, y);
			
			var normalStamp:Stamp  = new Stamp(Assets.BUTTON);
			
			label = new Text(text, 10, 0, { size: 16, color: 0x000000, width: normalStamp.width - 30, wordWrap: true, align: "center" } );
			label.y = (normalStamp.height - label.textHeight) * 0.5;
			
			normal = new Graphiclist(normalStamp, label);
			hover = new Graphiclist(new Stamp(Assets.BUTTON_HOVER), label);
			down = new Graphiclist(new Stamp(Assets.BUTTON_DOWN), label);
			
			graphic = normal;
			
			setHitboxTo(normalStamp);
		}

So if we add some text in the button we created in our TestWorld, we will see something like this:


Step 23: The Problem With the Text

There is a little problem with the way we are doing Text at the moment: we need to create a new Graphiclist for each graphic. This means we have to write more code for EACH graphic we have, and we can't have text on classes that extends us unless we also change that. A bit inconvenient.

So here's my solution: instead of inserting the text in our graphics, we'll just render it manually by overriding the render function.

Here's a renderGraphic function I created, which mimics the rendering of the Entity class, but accepts the graphic as a parameter. We will add this to our button:

		protected function renderGraphic(graphic:Graphic):void
		{
			if (graphic && graphic.visible)
			{
				if (graphic.relative)
				{
					_point.x = x;
					_point.y = y;
				}
				else _point.x = _point.y = 0;
				_camera.x = world ? world.camera.x : FP.camera.x;
				_camera.y = world ? world.camera.y : FP.camera.y;
				graphic.render(renderTarget ? renderTarget : FP.buffer, _point, _camera);
			}
		}
		protected var _point:Point = FP.point;
		protected var _camera:Point = FP.point2;

Now we will set the graphics as they were before:

		public function Button(x:Number=0, y:Number=0, text:String = "") 
		{
			super(x, y);
			
			var normalStamp:Stamp  = new Stamp(Assets.BUTTON);
			
			label = new Text(text, 10, 0, { size: 16, color: 0x000000, width: normalStamp.width - 30, wordWrap: true, align: "center" } );
			label.y = (normalStamp.height - label.textHeight) * 0.5;
			
			normal = normalStamp;
			hover = new Stamp(Assets.BUTTON_HOVER);
			down = new Stamp(Assets.BUTTON_DOWN);
			
			graphic = normal;
			
			setHitboxTo(normalStamp);
		}

And finally we will override the render function, to render the text as well as the button graphic:

		override public function render():void 
		{
			super.render();
			
			renderGraphic(label);
		}

It's time to add text to our Checkbox as well! First of all, we must accept the text as a string. Then, as we want it completely different (width / height and color), we will replace the label, as following:

		public function Checkbox(x:Number=0, y:Number=0, text:String = "") 
		{
			super(x, y, text);
			
			normal = new Image(Assets.CHECKBOX, new Rectangle(0, 0, 38, 34));
			hover = new Image(Assets.CHECKBOX, new Rectangle(38, 0, 38, 34));
			down = new Image(Assets.CHECKBOX, new Rectangle(76, 0, 38, 34));
			
			normalChecked = new Image(Assets.CHECKBOX, new Rectangle(0, 34, 38, 34));
			hoverChecked = new Image(Assets.CHECKBOX, new Rectangle(38, 34, 38, 34));
			downChecked = new Image(Assets.CHECKBOX, new Rectangle(76, 34, 38, 34));
			
			label = new Text(text, 40, 0, { color: 0xFFFFFF, size: 16 } );
			label.y = (Image(normal).height - label.textHeight) * 0.5;
			
			graphic = normal;
			setHitboxTo(normal);
		}

And, thanks to the power of extending, to have text in our RadioButtons, we only have to accept the text parameter and send it to super (and center it vertically)!

		public function RadioButton(x:Number=0, y:Number=0, group:RadioButtonGroup = null, text:String = "") 
		{
			super(x, y, text);
			
			if (group) group.add(this);
			
			normal = new Image(Assets.RADIOBUTTON, new Rectangle(0, 0, 39, 44));
			hover = new Image(Assets.RADIOBUTTON, new Rectangle(39, 0, 39, 44));
			down = new Image(Assets.RADIOBUTTON, new Rectangle(78, 0, 39, 44));
			
			normalChecked = new Image(Assets.RADIOBUTTON, new Rectangle(0, 44, 39, 44));
			hoverChecked = new Image(Assets.RADIOBUTTON, new Rectangle(39, 44, 39, 44));
			downChecked = new Image(Assets.RADIOBUTTON, new Rectangle(78, 44, 39, 44));
			
			label.y = (Image(normal).height - label.textHeight) * 0.5;
			
			graphic = normal;
			setHitboxTo(normal);
		}

In some applications and UIs, the "hitbox" of the checkbox / radiobutton also includes its text, so we can include that with a simple line in the Checkbox class, at the end of the constructor (and in RadioButton as well):

			width = label.x + label.textWidth;

If we add the text in our TestWorld, and test the project, we will have something like this:


Step 25: Custom Parameters: Callbacks

So now we can create different buttons with different text easily, to differentiate them... but at the moment, all our buttons do is change the background color. There's no use in having zillions of buttons if all of them... well, just change the background color. If we wanted to have different functionalities, with the current code, what we'd have to do is create a Button class for each functionality a button will do... that's a no-no!

What we're going to do instead is use a really similar approach to the text problem: we'll add a parameter for the functionality. This parameter will be simply a function. We'll send a function name to it, and when the button is clicked, the function will be called. This way we can have in our World three functions: gotoGame, gotoOptions and gotoCredits. Then, we can link each of them to the Play button, the Options button and the Credits button.

Doing this is extremely easy. We just need to accept the callback parameter, store it in our Button variables, and call it on the click function. Yay! We'll also remove the background color code.

Part of the button class variables and part of its constructor:

		//[...]
		protected var label:Text;
		public var callback:Function;
		
		public function Button(x:Number=0, y:Number=0, text:String = "", callback:Function = null) 
		{
			super(x, y);
			
			this.callback = callback;
			//[...]
		}

And the click function, only calling the callback if a callback was provided:

		protected function click():void
		{
			if (callback != null) callback();
		}

That's it... but I want to add an extra feature for the checkboxes. The whole point of creating a checkbox is being able to determine if it's checked or unchecked. The current way to do it is keep a reference of the checkbox in the World class, and then check its clicked property on the callback... but it's a bit tedious having to mantain references to each checkbox in our world, if we have many of them. So what we're going to do is: send a boolean parameter to the callback, with the value of the checked property. So, for the checkbox callback, we'll have to accept a boolean, the value of which will be the checked value of the checkbox. Nice!

Here's the new checkbox click function:

		override protected function click():void 
		{
			checked = !checked;
			
			if (callback != null) callback(checked);
		}

Remember to also ask for the callback parameter, and send it to super, as we show in this fragment of the Checkbox constructor:

		public function Checkbox(x:Number=0, y:Number=0, text:String = "", callback:Function = null) 
		{
			super(x, y, text, callback);
			//[...]
		}

And in case of the radiobuttons, I think we need a different approach. Usually, with a radiobutton, you don't want to determine the click of each individual radiobutton... what you want to do is determine which is the new clicked button in the group. That's why I think the callback shouldn't be on the button itself, but on the RadioButton group. Moreover, we also want this callback to provide information to determine which radiobutton was clicked. This can be sorted easily by demanding an extra parameter on the RadioButton: an ID, and sending that to the callback.

So, first of all, we add the id parameter on the radiobuttons, as shown in this fragment of the RadioButton constructor.

		public var id:String = "";
		
		public function RadioButton(x:Number=0, y:Number=0, group:RadioButtonGroup = null, text:String = "", id:String = "") 
		{
			super(x, y, text);
			
			this.id = id;
			//[...]
		}

Then, we send it to the group in the click function.

		override protected function click():void 
		{
			group.click(this, id);
		}

Now it's the turn of the RadioButtonGroup. First, we add the callback parameter:

		public var callback:Function = null
		
		public function RadioButtonGroup(callback:Function = null, ...buttons) 
		{
			this.callback = callback;
			
			//we add the buttons provided to the constructor, if any
			if (buttons) addList(buttons);
		}

Then, we call it with the id provided when a RadioButton from the group is clicked:

		internal function click(target:RadioButton, id:String):void
		{
			if (callback != null) callback(id);
			
			for each(var b:RadioButton in buttons) b.checked = false;
			target.checked = true;
		}

That's it!


Step 26: Custom Parameters: Custom Parameters

Custom parameterception! Basically, let's say we have a game with 30 levels. We want to create a Level Screen, where the user can select the level they want to play. If we had to create a function to go to each level, it would be a pain... really simpler if we were able to pass the level number to the callback! This way, we assign the level number when creating the button from, possibly, a loop, and then the callback reads the number and shows us the correspondant level.

If we asked for an extra argument called level number, though, this solution wouldn't work for any other case where we need extra parameters. What we are going to do instead is, ask for an optional parameter Object. As it will be an object, it can be an int, a String, a custom class, even an array to contain multiple params... or an Object itself, in this syntax: {param: "value", param2: 1, param3: false}. This way, we can have multiple parameters which can be retrieved like this: object.param, object.param2, etc.

To implement this is really simple. We just need to ask for an extra param, called params, and send it to the callback. You should be able to do it on your own, but just in case, here's the code for Button and Checkbox:

		protected var label:Text;
		public var callback:Function;
		public var params:Object;
		
		public function Button(x:Number=0, y:Number=0, text:String = "", callback:Function = null, params:Object = null) 
		{
			super(x, y);
			
			this.callback = callback;
			this.params = params;
			//[...]
		}
		
		//[...]
		
		protected function click():void
		{
			if (callback != null)
			{
				if (params != null) callback(params);
				else callback();
			}
		}

And now the checkbox:

		public function Checkbox(x:Number=0, y:Number=0, text:String = "", callback:Function = null, params:Object = null) 
		{
			super(x, y, text, callback, params);
			
			//[...]
		}
		
		override protected function click():void 
		{
			checked = !checked;
			
			if (callback != null)
			{
				if (params != null) callback(checked, params);
				else callback(checked);
			}
		}

Now, for the RadioButton, we want the params to be in the button, but they will be transfered to the group and sent using the group callback... wait a minute! We already have something which does just like that! But it's called ID and it is a String. I think we can safely say we can change ID to params and set it to object, and we will be able to use it for the same reason as ID and as extra params as well.

Basically, we need to remove the id variable on the RadioButton and rename the param in the constructor:

		public function RadioButton(x:Number=0, y:Number=0, group:RadioButtonGroup = null, text:String = "", params:Object = null) 
		{
			super(x, y, text, null, params);
			
			//[...]
		}

Then we send the params on the click function instead of the id:

		override protected function click():void 
		{
			group.click(this, params);
		}

And we change the click function on the group reflecting these changes as well:

		internal function click(target:RadioButton, params:Object):void
		{
			if (callback != null) callback(params);
			
			for each(var b:RadioButton in buttons) b.checked = false;
			target.checked = true;
		}

Step 27: Arcade Text Input

Text Inputs (text fields) are commonly used in games. Most games need at least one text input from the user: to set their name for the score. Depending on the game, there might be other uses for text inputs. Now we're going to learn how to make them in FlashPunk, without using the AS3 TextField.

Note that, by implementing the TextInput directly in FlashPunk, there are some kind of characters, like accents (àáèéìíòóùú) which can't be typed. If you need those, you may want to add AS3 TextFields to FlashPunk directly by adding them on the stage (FP.stage.addChild) or to the engine MovieClip (FP.engine.addChild). Those won't really be necessary if you are using English text, but other languages have them so keep that in mind.

First of all, we'll build the TextInput component. The class will extend Entity, and will have two custom properties: text, as a String, and textGraphic, of the type Text. text will contain the text inputted in the TextInput, and textGraphic will be the visual representation of it. We will implement text as a getter/setter, because when we change it through code, we also want to update the visual representation.

package ui 
{
	import net.flashpunk.Entity;
	import net.flashpunk.graphics.Text;
	
	public class TextInput extends Entity 
	{
		protected var _text:String = "";
		protected var textGraphic:Text;
		
		public function TextInput(x:Number=0, y:Number=0, text:String = "") 
		{
			super(x, y);
			
			textGraphic = new Text("", 0, 0, { size: 16, color: 0xFFFFFF } );
			this.text = text;
			graphic = textGraphic;
		}
		
		public function get text():String 
		{
			return _text;
		}
		
		public function set text(value:String):void 
		{
			_text = value;
			textGraphic.text = value;
		}
	}
}

Now we need to check for each key and write them on the text string... or use a really helpful variable provided in the Input class! The variable is called keyString, and registers the last 100 keys that were written by the user. This way, we can retrieve the string to know what the user typed. The simplest way is to add an update function to our textField, add all the keys in the variable to our text, and clear the variable so we don't duplicate the keys we already retrieved.

		override public function update():void 
		{
			super.update();
			
			if (Input.keyString != "")
			{
				text += Input.keyString;
				Input.keyString = "";
			}
		}

Step 28: Erasing Text and Multiline Text

One thing the Input keyString variable doesn't support is erasing text. We must add the functionality by hand. But it's really easy, we just check if the backspace key was pressed, and if it was, delete a character from the string.

		override public function update():void 
		{
			super.update();
			
			if (Input.keyString != "")
			{
				text += Input.keyString;
				Input.keyString = "";
			}
			
			if (Input.pressed(Key.BACKSPACE)) text = _text.substr(0, _text.length - 1);
		}

The problem is that this method won't erase if the user keeps the backspace key pressed. To do so, we will use a KeyboardEvent, which we will add to the stage. We will also remove the event when the text input is removed, and it won't do anything if it detects the current world is not the world the text is in.

		override public function added():void 
		{
			super.added();
			
			FP.stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
		}
		
		protected function onKeyDown(e:KeyboardEvent):void
		{
			if (world != FP.world) return;
			if (e.keyCode != Key.BACKSPACE) return;
			
			text = _text.substr(0, _text.length - 1);
		}
		
		override public function removed():void 
		{
			super.removed();
			
			FP.stage.removeEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
		}

From here, adding multiline is really simple. We just need to check for the Enter key on the onKeyDown function, and insert the "\n" character to the string:

		protected function onKeyDown(e:KeyboardEvent):void
		{
			if (world != FP.world) return;
			
			if (e.keyCode == Key.BACKSPACE) text = _text.substr(0, _text.length - 1);
			else if (e.keyCode == Key.ENTER) text += "\n";
		}

We could even add a boolean to allow or not multiline. This way we can have the "textfields" and the "textareas". First, we add it to our variables and ask it as an optional parameter for the constructor:

		protected var multiline:Boolean = false;
		
		public function TextInput(x:Number=0, y:Number=0, text:String = "", multiline:Boolean = false) 
		{
			super(x, y);
			
			this.multiline = multiline;
			
			textGraphic = new Text("", 0, 0, { size: 16, color: 0xFFFFFF } );
			this.text = text;
			graphic = textGraphic;
		}

Then, we just add the check to the onKeyDown function.

		protected function onKeyDown(e:KeyboardEvent):void
		{
			if (world != FP.world) return;
			
			if (e.keyCode == Key.BACKSPACE) text = _text.substr(0, _text.length - 1);
			else if (e.keyCode == Key.ENTER && multiline) text += "\n";
		}

Now, if you add a multiline textfield to your world, this is approximately what you will see:


Step 29: Multiple Text Operations

If you try adding two TextInputs at the moment, what you're going to see is: normal characters will only work on the most recently added TextField, while erasing and new line keys will work on all of them at once. What we want to achieve is that the user can only type to one textfield at the same time.

This can be achieved easily by building some kind of "focus" system. At first, no textfield will be focused, so typing won't do anything. When the user clicks on a textfield, the clicked textfield will be focused, so typing will work, ONLY on that textfield. If then we click another one, typing will work ONLY on the newly selected one. If we then click to somewhere with no textfields, we will remove focus to the previously selected, so typing won't do anything again.

There are two simple ways I can come up with to solve this problem. One involves having a custom World class, the other involves a static variable. As creating a new world class which will have to be used in order to create textinputs is a bit of a hassle, we'll go for the static variable approach. We will have a static variable in our TextInput class, which will contain the focused TextInput. When checking for the type, we will check before if we are this focused TextInput, if we aren't, we'll simply ignore the typing. Moreover, when detecting a click on the textfield, we'll set that variable to ourselves. Finally, we'll build some system to deselect the text inputs, but we'll talk about it later.

First, we create the static variable, on the TextInput class:

		public static var focus:TextInput;

Then we add the checks in the update function:

		override public function update():void 
		{
			super.update();
			
			if (TextInput.focus == this && Input.keyString != "")
			{
				text += Input.keyString;
				Input.keyString = "";
			}
		}

And the checks for the onKeyDown function as well:

		protected function onKeyDown(e:KeyboardEvent):void
		{
			if (world != FP.world) return;
			if (TextInput.focus != this) return;
			
			if (e.keyCode == Key.BACKSPACE) text = _text.substr(0, _text.length - 1);
			else if (e.keyCode == Key.ENTER && multiline) text += "\n";
		}

Finally, we have to enable the selection of the TextField. We'll make a dynamic hitbox by adjusting its width and height everytime the text changes, to match exactly the text size:

		public function set text(value:String):void 
		{
			_text = value;
			textGraphic.text = value;
			
			setHitbox(textGraphic.textWidth, textGraphic.textHeight);
		}

Then, we check for the click an set the focus to us if there is one:

		override public function update():void 
		{
			super.update();
			
			if (Input.mousePressed && collidePoint(x, y, world.mouseX, world.mouseY)) TextInput.focus = this;
			
			if (TextInput.focus == this && Input.keyString != "")
			{
				text += Input.keyString;
				Input.keyString = "";
			}
		}

Add another textinput to the world and try to type something. Nothing will happen. Now select one of them and type, you will see typing, erasing and enter (if multiline is enabled) will work only on the selected textinput. You can switch them as well.


Step 30: Ghost Writing and Unselecting

There is a little bug in our TextInput at the moment. Open the swf, type something without having any textinput selected, and then click at one of them. Poof! A ghost has left a message for you!

Don't worry, there is no ghost. The problem is, we remove the keyString from Input on the update, only if the textfield is selected. Thus, if there's no selection, we aren't removing it, and when we select a new textfield, there's the whole last 100 characters we typed appearing at once.

Luckily, it's very easy to solve that. We just need to clear the keyString immediately after selecting a new textfield. So, on the update function, update the selection code to this:

		override public function update():void 
		{
			super.update();
			
			if (Input.mousePressed && collidePoint(x, y, world.mouseX, world.mouseY))
			{
				TextInput.focus = this;
				Input.keyString = "";
			}
			
			if (TextInput.focus == this && Input.keyString != "")
			{
				text += Input.keyString;
				Input.keyString = "";
			}
		}

Next thing: unselecting. To unselect, we'll make an additional check when the mouse is clicked and the TextInput was not selected: we'll check if there's any TextInput under the mouse. If there isn't, we set focus to null.

To do that, we'll set our type property. If you have used FlashPunk before, you'll know type is a property used on Entities for collision detection. Usually, when you check for collisions, you check against a type, this way you can filter collisions (for example, if you want to check collision against enemies to recude health, you will only check collisions with the enemy type, so you don't perform unnecessary tasks by checking collision with the walls as well).

In our case, the type will be unique to the textInputs, and will be used for the collision check. FlashPunk world has a method for checking if there's an entity of a type under a point. We will check if there's any textInput under the mouse point. If there aren't, we'll set focus to null.

First, we set the type in the constructor:

		public function TextInput(x:Number=0, y:Number=0, text:String = "", multiline:Boolean = false) 
		{
			super(x, y);
			
			this.multiline = multiline;
			
			textGraphic = new Text("", 0, 0, { size: 16, color: 0xFFFFFF } );
			this.text = text;
			graphic = textGraphic;
			
			type = "uiTextInput";
		}

Then, we expand the mouse pressed check and add an else condition on the collision with the mouse check, so we'll check if the user has pressed any textinput at all when clicking the mouse:

		override public function update():void 
		{
			super.update();
			
			if (Input.mousePressed)
			{
				if (collidePoint(x, y, world.mouseX, world.mouseY))
				{
					TextInput.focus = this;
					Input.keyString = "";
				}
				else if (!world.collidePoint("uiTextInput", world.mouseX, world.mouseY)) TextInput.focus = null;
			}
			
			if (TextInput.focus == this && Input.keyString != "")
			{
				text += Input.keyString;
				Input.keyString = "";
			}
		}

That's it. Now, we'll add an extra feature: we'll change the color of the text if it's focused. It will be light blue if focused, and completely white if not. To do this, we just need to add these two lines somewhere in the update function:

		override public function update():void 
		{
			super.update();
			
			if (TextInput.focus == this) textGraphic.color = 0xC4DCF4;
			else textGraphic.color = 0xFFFFFF;
			
			//[...]
		}

And that's how it will look like, more or less:


Step 31: Final Demo

We're done now, we've created an extensive set of components. You'll be able to create some good game UIs with those. Here's an example of a simple pet creation application for a game, using the UI we just built:

Here is the code. The images used are available on the source download, as well as the code itself. Basically what it's doing is showing an image depending on the type selected, then hiding or showing transparent images depending on the features selected:

package  
{
	import net.flashpunk.FP;
	import net.flashpunk.graphics.Graphiclist;
	import net.flashpunk.graphics.Image;
	import net.flashpunk.graphics.Stamp;
	import net.flashpunk.graphics.Text;
	import net.flashpunk.World;
	import ui.Button;
	import ui.Checkbox;
	import ui.RadioButton;
	import ui.RadioButtonGroup;
	import ui.TextInput;
	
	public class PetCreator extends World 
	{
		private var nameInput:TextInput;
		
		private var fishRad:RadioButton;
		private var octopusRad:RadioButton;
		private var snakeRad:RadioButton;
		
		private var hatCheck:Checkbox;
		private var wingsCheck:Checkbox;
		private var laserCheck:Checkbox;
		private var friendCheck:Checkbox;
		
		[Embed(source = "../assets/gfx/fish.png")] public static const FISH:Class;
		[Embed(source = "../assets/gfx/friend.png")] public static const FRIEND:Class;
		[Embed(source = "../assets/gfx/hat.png")] public static const HAT:Class;
		[Embed(source = "../assets/gfx/laser.png")] public static const LASER:Class;
		[Embed(source = "../assets/gfx/octopus.png")] public static const OCTOPUS:Class;
		[Embed(source = "../assets/gfx/snake.png")] public static const SNAKE:Class;
		[Embed(source = "../assets/gfx/wing.png")] public static const WING:Class;
		
		private var fish:Stamp;
		private var octopus:Stamp;
		private var snake:Stamp;
		
		private var friend:Stamp;
		private var hat:Stamp;
		private var laser:Stamp;
		private var wing:Stamp;
		
		public function PetCreator() 
		{
			addGraphic(new Text("Pet Creator", 10, 10, { size: 32, color: 0xFFFFFF } ));
			
			//=============
			
			fish = new Stamp(FISH, 250, 20);
			octopus = new Stamp(OCTOPUS, 250, 20);
			snake = new Stamp(SNAKE, 250, 20);
			
			friend = new Stamp(FRIEND, 250, 20);
			hat = new Stamp(HAT, 250, 20);
			laser = new Stamp(LASER, 250, 20);
			wing = new Stamp(WING, 250, 20);
			
			octopus.visible = snake.visible = false;
			friend.visible = hat.visible = laser.visible = wing.visible = false;
			
			addGraphic(new Graphiclist(fish, octopus, snake, friend, hat, laser, wing));
			
			//===============
			
			addGraphic(new Text("Name:", 10, 80, { size: 16, color: 0x8CD5FB } ));
			add(nameInput = new TextInput(80, 80, "Pikachu"));
			
			addGraphic(new Text("Type:", 10, 120, { size: 16, color: 0x8CD5FB } ));
			
			var typeGroup:RadioButtonGroup = new RadioButtonGroup(onType);
			add(fishRad = new RadioButton(10, 150, typeGroup, "Fish", fish, true));
			add(octopusRad = new RadioButton(100, 150, typeGroup, "Octopus", octopus));
			add(snakeRad = new RadioButton(10, 200, typeGroup, "Snake", snake));
			
			addGraphic(new Text("Features:", 10, 270, { size: 16, color: 0x8CD5FB } ));
			add(hatCheck = new Checkbox(10, 300, "Hat", onFeature, hat));
			add(wingsCheck = new Checkbox(120, 300, "Wings", onFeature, wing));
			add(laserCheck = new Checkbox(10, 340, "Laser", onFeature, laser));
			add(friendCheck = new Checkbox(120, 340, "Friend", onFeature, friend));
			
			add(new Button(320, 290, "Random", onRandom));
		}
		
		private function onType(params:Object):void
		{
			fish.visible = octopus.visible = snake.visible = false;
			params.visible = true;
		}
		
		private function onFeature(on:Boolean, params:Object):void
		{
			params.visible = on;
		}
		
		private function onRandom():void
		{
			fish.visible = octopus.visible = snake.visible = false;
			fishRad.checked = octopusRad.checked = snakeRad.checked = false;
			
			var type:int = Math.random() * 3;
			if (type < 1)
			{
				fish.visible = true;
				fishRad.checked = true;
			}
			else if (type < 2)
			{
				octopus.visible = true;
				octopusRad.checked = true;
			}
			else if (type < 3)
			{
				snake.visible = true;
				snakeRad.checked = true;
			}
			
			hatCheck.checked = hat.visible = Math.random() < 0.5;
			wingsCheck.checked = wing.visible = Math.random() < 0.5;
			laserCheck.checked = laser.visible = Math.random() < 0.5;
			friendCheck.checked = friend.visible = Math.random() < 0.5;
			
			nameInput.text = FP.choose("Pikachu", "Link", "Slim Shady", "Abel Toy", "Me", "Meat Pet", "Daddy",
			"Leonardo DiCaprio", "Mr. Burns", "Peter", "TYPE YOUR NAME", "Derp");
		}
	}
}

Step 31: Conclusion

This is the end of this first FlashPunk UI tutorial. Here I taught you how to build the components without any customization or eye-candy. In the next parts, we will learn how to customize the graphical side of our components. We'll also learn how to draw them by code directly, how to add some animation to the states change, how to add some particle fun on them, and so on.

We'll also expand the TextInput a little bit with a really needed background, and some blinking text indicator, like the real textfields. We will also learn how to make an advanced component, the Slider, which is basically a very special button. Using that Slider we'll also learn how to make a scrolling bar.

Moreover, we will improve the collision checks. We will learn basically how to detect pixel-perfect collisions with the components, so if we have a really irregular-shaped button, only clicking ON it will make it work, not its whole bounding box.

I hope you learned how to make your own UI components, and that you liked the tutorial! Leave me any feedback, suggestions, and so on in the comments below. Thanks!