Making a game with Flash Develop + Flixel: Part 3, Input and Objects

Previously:

(Want to bypass the long explanations and see the final product? Here, download this post’s code as a ZIP file.)

(Unlike the last part, I will be spending more time explaining how most things work and the reasoning behind moving lines in this post. Now that we have our files created and a framework in place from Part 2, we can build on it to deal with player input and use animations.)

In Part 2, I very quickly glossed over creating a FlxSprite and adding it to the current state as part of the PlayState.

var background:FlxSprite;
background = new FlxSprite();
background.makeGraphic(100, 100);
add(background);
view raw PlayState.as hosted with ❤ by GitHub

A FlxSprite though is a integral part of Flixel. You can think of them as being anything drawable. They come with built-in functionality such as basic positioning (x, y) as well as advanced methods like animations and velocity tracking.

They also work in two different modes: make and load. In Part 2, I was using the “make” method, calling makeGraphic and then supplying the arguments of how I wanted the rectangle to be created. They can also, in the “load” mode, use calls to loadGraphic to do exactly that, load an image class.

Right now, I am going to use the makeGraphic method for this post.

Here is a new version of PlayState:

package
{
/**
* ...
* @author Dan Cox
*/
import org.flixel.*;
public class PlayState extends FlxState
{
private var background:FlxSprite;
public function PlayState()
{
background = new FlxSprite();
background.makeGraphic(100, 100);
add(background);
}
override public function update():void
{
super.update();
}
}
}
view raw PlayState.as hosted with ❤ by GitHub

Notice that I moved the definition of background outside of the PlayState constructor. I did this to shift its scope to beyond the constructor and as part of the entire object. Now, any function member of PlayState can access background.

I also created an override of the update function. The reason for this is because we need to actually specify how to update. The call to its super (parent) will now take care of the same code as it normally would, but we can add some of our own code into what to do for each call to update.

(Note: If this seems a little unfamiliar, think about it like this. The constructor gets called first. This is the “setup” phase. Once everything has been created, the “update” phase is called. Here, sprites are updated and different logic happens. Finally, the “draw” phase happens and things are, well, drawn. The code then loops the update to draw cycle until the game ends.)

In fact, with our own override of update, we can handle the user’s input and then change the position of our background sprite before it is drawn. To do that, we need to test for if keys are pressed.

Luckily, Flixel supplies three different ways to do this. The first and easiest way is with an if-statement on the FlxG.keys object. For every key we want to test, we need only test for FlxG.keys.W, for example. If that particular key, ‘W’ in this case, is currently being pressed, we will get a true value.

The other two are related in that they are both function calls instead of properties. We can test for FlxG.keys.justPressed(“W”) to see if the ‘W’ key was just pressed but not necessary released and then use FlxG.keys.justReleased(“W”) when the ‘W’ key is released finally. Depending on your needs, both are quite useful.

However, for simplicity, I’ll be using the first way.

package
{
/**
* ...
* @author Dan Cox
*/
import org.flixel.*;
public class PlayState extends FlxState
{
private var background:FlxSprite;
public function PlayState()
{
background = new FlxSprite();
background.makeGraphic(100, 100);
add(background);
}
override public function update():void
{
super.update();
if (FlxG.keys.W || FlxG.keys.UP)
{
background.y -= 3;
}
else if (FlxG.keys.S || FlxG.keys.DOWN)
{
background.y += 3;
}
else if (FlxG.keys.A || FlxG.keys.LEFT)
{
background.x -= 3;
}
else if (FlxG.keys.D || FlxG.keys.RIGHT)
{
background.x += 3;
}
}
}
}
view raw PlayState.as hosted with ❤ by GitHub

Notice that I’m using the OR operator “||” to test if either of two keys are pressed. While I personally prefer to use WASD to move a character, many people like using the Arrow Keys instead. By testing for both, we can offer different ways to move things.

I’m also using a chain of if-else-if statements too. Normally, it might be enough to use four different if-statements to test for each direction. However, I wanted to limit how the square is moved. With the if-else collection, the square can only be moved in one direction at a time. It cannot move diagonally.

As a final note for this, I’m changing the x and y position of background if either of each buttons is currently being pressed. As I wrote at the top of this post about FlxSprites, they come with built-in positioning. By changing the values of those x or y coordinates, it will change where it is drawn on screen.

However, with all this code dedicated to just creating and then moving around background, it might be time to make a new object. That way, everything that has to do with background can be in one file.

In fact, let’s make background become our new ‘Player’ object by creating a class, PlayerSprite, have it inherit from FlxSprite and then handle all of its specific input in that class.

(Note: As a reminder, to create and add a new class to this project, select the “src” directory, right-click and then choose New and “New Class…”.)

package
{
/**
* ...
* @author Dan Cox
*/
import org.flixel.*;
public class PlayerSprite extends FlxSprite
{
public function PlayerSprite(X:Number = 0, Y:Number = 0)
{
super(X, Y);
makeGraphic(100, 100);
}
override public function update():void
{
super.update();
if (FlxG.keys.W || FlxG.keys.UP)
{
y -= 3;
}
else if (FlxG.keys.S || FlxG.keys.DOWN)
{
y += 3;
}
else if (FlxG.keys.A || FlxG.keys.LEFT)
{
x -= 3;
}
else if (FlxG.keys.D || FlxG.keys.RIGHT)
{
x += 3;
}
}
}
}
view raw PlayerSprite.as hosted with ❤ by GitHub

Just like we had in PlayState, we need to have our own override of FlxSprite’s update in order to handle input.

We can also drop the “background” from all the properties because the object PlayerSprite is a FlxSprite.

As part of its constructor, we also specify that the x and y values be 0 if no other value is passed in during its instantiation. We then use these values, 0 or otherwise, on its super class FlxSprite to make sure it is set to something. (In order to be drawn on the screen, FlxSprites must be somewhere.)

And, now that we have PlayerSprite, we need to go back and change PlayState to account for this.

package
{
/**
* ...
* @author Dan Cox
*/
import org.flixel.*;
public class PlayState extends FlxState
{
private var player:PlayerSprite;
public function PlayState()
{
player = new PlayerSprite();
add(player);
}
override public function update():void
{
super.update();
}
}
}
view raw PlayState.as hosted with ❤ by GitHub

Now, we need only create a new PlayerSprite object and then add it to the state. The updating will happen as part of the call to PlayState’s super.update which will, in turn, call each state-added object’s own update functionality.

In other words, we can create our own versions of different Flixel objects. Then, as long as we add them to a different state (like PlayState), they will update themselves. We only need to worry about matching up the objects with our own designs.

In Part 4, I’ll continue this trend with Tilemaps and collisions.