AS3 Avoider Game Tutorial, Part 7: Keyboard Control

by Michael Williams on February 17, 2009 · 136 comments

in Avoider Game Base, Tutorial

In this part of my AS3 and Flash CS3 conversion of Frozen Haddock’s avoider game tutorial, we’ll add keyboard controls. Don’t worry, we’ll keep the code separate so that it won’t interfere with the mouse controls we’ve already put in!

Click the image below to see how this will play:

screenshot

If you’ve not been following the tutorial, grab the zip file of the game so far here and extract it somewhere. Otherwise, copy the files you’ve been working on so far to a new folder, as usual.

Open the FLA file, and let’s get started.

Switching Control Schemes

Rather than removing the mouse controls entirely, let’s set things up so that we can switch between the two control schemes easily. Open AvoiderGame.as, and create a new Boolean variable at the class level named useMouseControl:

?View Code ACTIONSCRIPT3
8
9
10
11
12
13
14
public class AvoiderGame extends MovieClip 
{
	public var army:Array;
	public var enemy:Enemy;
	public var avatar:Avatar;
	public var gameTimer:Timer;
	public var useMouseControl:Boolean;

Remember, a Boolean is a variable that can only be one of two values: true or false. We’re going to use it as a “toggle”; if it’s true, mouse controls will work and keyboard controls won’t; if it’s false, keyboard controls will work and mouse controls won’t.

Since we haven’t written the keyboard control code yet, let’s set it to true so that we can test it out. The constructor is a fine place to do this:

?View Code ACTIONSCRIPT3
16
17
18
public function AvoiderGame() 
{
	useMouseControl = true;

Since useMouseControl is defined at the class level, it’ll be available throughout AvoiderGame.

At the moment, there are only two places where we’re referring to the mouse’s position in code. The first is in the constructor, where we set the initial position of the avatar to be wherever the cursor is:

?View Code ACTIONSCRIPT3
25
26
27
28
avatar = new Avatar();
addChild( avatar );
avatar.x = mouseX;
avatar.y = mouseY;

If the player’s not using the mouse to control the game, it’s going to be pretty annoying for them to have the avatar start out wherever the mouse happens to be. Let’s change that:

?View Code ACTIONSCRIPT3
25
26
27
28
29
30
31
32
33
34
35
36
avatar = new Avatar();
addChild( avatar );
if ( useMouseControl )
{
	avatar.x = mouseX;
	avatar.y = mouseY;
}
else
{
	avatar.x = 200;
	avatar.y = 250;
}

The code in the else section is only going to be run if the player is using keyboard control, and will place the avatar at the bottom-centre of the screen (adjust the numbers depending on what you think is best).

Before we test this, let’s change the other bit of code that references the mouse. It’s in the onTick() function and it’s exactly the same:

?View Code ACTIONSCRIPT3
54
55
avatar.x = mouseX;
avatar.y = mouseY;

Not surprisingly, our change will be very similar to before:

?View Code ACTIONSCRIPT3
54
55
56
57
58
59
60
61
62
if ( useMouseControl )
{
	avatar.x = mouseX;
	avatar.y = mouseY;
}
else
{
	//no keyboard code yet!
}

(Remember, line 61 is a comment and will be ignored by Flash; it’s just there as a reminder to us.)

If you save this and run it… well, you won’t see anything different, because we’ve set useMouseControl to true. That’s good, it means we can do whatever we like regarding keyboard control and it won’t mess up our existing mouse control code.

Change useMouseControl to false (in the constructor) and run it again.

screenshot

You’ll find the avatar starts at the bottom-centre of the screen and cannot be moved by the mouse. Great! Well, not so great for the player. But we’ll fix that.

Going Down

Remember when we first programmed the Enemy, and wrote the function moveDownABit()? By increasing the y-position of the enemy a little bit every tick, we simulated downwards motion. Let’s use the same strategy for the Avatar. Open up Avatar.as. It’s almost entirely empty — how exciting:

?View Code ACTIONSCRIPT3
1
2
3
4
5
6
7
8
9
10
11
package 
{
	import flash.display.MovieClip;
	public class Avatar extends MovieClip 
	{
		public function Avatar() 
		{
 
		}
	}
}

Add the new function:

?View Code ACTIONSCRIPT3
6
7
8
9
10
11
12
13
14
public function Avatar() 
{
 
}
 
public function moveDownABit():void
{
	y = y + 2;
}

Yep, this goes against what I said in the last part about hard-coding values. Don’t worry, we’ll change it soon.

The enemy objects have their movement functions called exactly once per tick. We need to call the appropriate movement functions for the avatar once per tick… but how do we know when it’s appropriate?

Ideally, we’d do something like this (in the onTick() function):

?View Code ACTIONSCRIPT3
54
55
56
57
58
59
60
61
62
63
64
65
if ( useMouseControl )
{
	avatar.x = mouseX;
	avatar.y = mouseY;
}
else
{
	if ( downKeyIsBeingPressed )
	{
		avatar.moveDownABit();
	}
}

In AS2 we could have done exactly that — but not in AS3. Now we’re living in the futuristic world of Events, and we have to do a little more to make this work.

You see, we can’t detect whether a given key is being pressed at a given time — that functionality is gone. But with event listeners, we can detect when a given key has been pushed down, and we can also detect when a given key has been lifted up again. If we have detected that a key has been pushed down and we have not yet detected that the same key has been lifted up, well, it’s a pretty safe bet that the key is being pushed down at that very moment, right?

[If this isn't clear, imagine that every time you press the B key, your computer says aloud "the B key has just been pressed", and every time you take your finger off the B key, your computer says "the B key is no longer being pressed". Someone listening to your computer would have a pretty good idea of whether or not you were pushing the B key even if they weren't looking at your hands.]

Enough talk. Let’s get the code in.

First, we need another Boolean to take note of whether the Down key is currently being pressed. Let’s call it downKeyIsBeingPressed. It needs to go in AvoiderGame.as, at the same places where we set up useMouseControl:

?View Code ACTIONSCRIPT3
14
15
16
17
18
19
20
public var useMouseControl:Boolean;
public var downKeyIsBeingPressed:Boolean;
 
public function AvoiderGame() 
{
	downKeyIsBeingPressed = false;
	useMouseControl = false;

(I’m assuming here that the player isn’t holding down the Down key as he starts the game.)

Now we need to detect when the player is pressing and releasing keys. You will probably not be surprised to find that we use event listeners for this. This should all be very simple to do, but unfortunately (and despite keyboard control being a Very Useful Feature for a lot of applications) Flash seems to go out of its way to make things confusing. Please bear with me through this next section, it’s not hard, just irritating :)

Flash vs. Developers

Let’s start by adding the event listeners. Add these lines to the end of the AvoiderGame.as constructor function:

?View Code ACTIONSCRIPT3
44
45
addEventListener( KeyboardEvent.KEY_DOWN, onKeyPress );
addEventListener( KeyboardEvent.KEY_UP, onKeyRelease );

Guess what? Yep, Flash needs to be told about KeyboardEvent, so import it at the top:

?View Code ACTIONSCRIPT3
3
4
5
6
7
import flash.display.MovieClip;
import flash.utils.Timer;
import flash.events.TimerEvent;
import flash.ui.Mouse;
import flash.events.KeyboardEvent;

First we’ll create the onKeyPress event listener, which will be triggered whenever a key is pushed down:

?View Code ACTIONSCRIPT3
49
50
51
52
public function onKeyPress( keyboardEvent:KeyboardEvent ):void
{
 
}

(Remember to create this outside of the constructor function but inside the class.)

How do we know which key is being pressed? Well, every key has a unique ID number, called a key code, and the event object stores the key code of whichever key triggered the event. Obviously it would be inconvenient if we had to remember every single ID, so Flash helps us out by giving us a class, Keyboard, that stores all these codes.

Of course, we have to import it:

?View Code ACTIONSCRIPT3
3
4
5
6
7
8
import flash.display.MovieClip;
import flash.utils.Timer;
import flash.events.TimerEvent;
import flash.ui.Mouse;
import flash.events.KeyboardEvent;
import flash.ui.Keyboard;

Go back to the onKeyPress event handler and add the following:

?View Code ACTIONSCRIPT3
50
51
52
53
54
55
56
public function onKeyPress( keyboardEvent:KeyboardEvent ):void
{
	if ( keyboardEvent.keyCode == Keyboard.DOWN )
	{
		downKeyIsBeingPressed = true;
	}
}

If you’re using Flash to write your AS files, then as soon as you type Keyboard. you’ll see a big list of all the different keys available appear.

The onKeyRelease event listener is very similar:

?View Code ACTIONSCRIPT3
58
59
60
61
62
63
64
public function onKeyRelease( keyboardEvent:KeyboardEvent ):void
{
	if ( keyboardEvent.keyCode == Keyboard.DOWN )
	{
		downKeyIsBeingPressed = false;
	}
}

Obviously it’s very important to have downKeyIsBeingPressed set to false here and true above.

Alright, so, in theory we should now be able to tell whether the Down key is currently being pressed from any point within AvoiderGame. This means we can add in our ideal code from earlier to the onTick() function:

?View Code ACTIONSCRIPT3
77
78
79
80
81
82
83
84
85
86
87
88
if ( useMouseControl )
{
	avatar.x = mouseX;
	avatar.y = mouseY;
}
else
{
	if ( downKeyIsBeingPressed )
	{
		avatar.moveDownABit();
	}
}

If you save it and run it this will not work. Sorry. We have to make a couple of changes first.

For one thing, Keyboard Event listeners have to be added to the stage. To quote the AS3 LiveDocs: “The Stage class represents the main drawing area. The Stage represents the entire area where Flash® content is shown.”

Simple enough. Go back to the constructor function and change the addEventListener calls from this:

?View Code ACTIONSCRIPT3
47
48
addEventListener( KeyboardEvent.KEY_DOWN, onKeyPress );
addEventListener( KeyboardEvent.KEY_UP, onKeyRelease );

to this:

?View Code ACTIONSCRIPT3
47
48
stage.addEventListener( KeyboardEvent.KEY_DOWN, onKeyPress );
stage.addEventListener( KeyboardEvent.KEY_UP, onKeyRelease );

Will this work now? No, we’ll get an error:

TypeError: Error #1009: Cannot access a property or method of a null object reference.

The problem is, stage is actually null at this point. Why? Well, an object only has access to stage if it’s been addChild-ed to the document class, or to another object that has been addChild-ed to the document class or… etc. But look at the code in DocumentClass.as:

?View Code ACTIONSCRIPT3
40
41
42
43
44
playScreen = new AvoiderGame();
playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
playScreen.x = 0;
playScreen.y = 0;
addChild( playScreen );

The AvoiderGame() constructor function — and therefore the stage.addEventListener() lines — is run when new AvoiderGame() is run. But the addChild( playScreen ) line — which is required for playScreen to actually have a stage — isn’t run until after that point.

We get around this by using another event listener. This one is going to be triggered when that addChild( playScreen ) line is called. We can then add the stage.addEventListener() lines within that event handler. Confusing? Let’s write the code, it’ll make it clearer.

First, in AvoiderGame.as, replace

?View Code ACTIONSCRIPT3
47
48
stage.addEventListener( KeyboardEvent.KEY_DOWN, onKeyPress );
stage.addEventListener( KeyboardEvent.KEY_UP, onKeyRelease );

with

?View Code ACTIONSCRIPT3
46
addEventListener( Event.ADDED_TO_STAGE, onAddToStage );

(Don’t delete the onKeyPress and onKeyRelease event handler functions, we’ll need them later!)

Naturally we need to import this Event class…

?View Code ACTIONSCRIPT3
3
4
5
6
7
8
9
import flash.display.MovieClip;
import flash.utils.Timer;
import flash.events.TimerEvent;
import flash.ui.Mouse;
import flash.events.KeyboardEvent;
import flash.ui.Keyboard;
import flash.events.Event;

Now, create a new event handler for this Event.ADDED_TO_STAGE event:

?View Code ACTIONSCRIPT3
50
51
52
53
54
public function onAddToStage( event:Event ):void
{
	stage.addEventListener( KeyboardEvent.KEY_DOWN, onKeyPress );
	stage.addEventListener( KeyboardEvent.KEY_UP, onKeyRelease );
}

Save it and run it. Hooray, it works!

Although… try pressing any of the other keys on the keyboard while the game is running. You’ll probably notice the mouse flashing to different symbols. If you have the FLA open, rather than an AS file, you’ll be able to see the different buttons in the toolbar being selected as you press different letter keys.

This is another of Stephen Calender’s obstacles for Flash game development, and it’s incredibly irritating if you want to use keys that also happen to be used by Flash. Fortunately, there is a solution. When your game is running, click Control > Disable Keyboard Shortcuts:

screenshot

Voilà, no more problems.

[Jake let me know that this behaviour still exists in CS4 -- but luckily, so does the solution.]

Four-Way Movement

It’s now pretty simple to set the code up to detect other keys. Just take what we’ve already done and multiply it.

Class variables:

?View Code ACTIONSCRIPT3
17
18
19
20
21
public var useMouseControl:Boolean;
public var downKeyIsBeingPressed:Boolean;
public var upKeyIsBeingPressed:Boolean;
public var leftKeyIsBeingPressed:Boolean;
public var rightKeyIsBeingPressed:Boolean;

Constructor function:

?View Code ACTIONSCRIPT3
23
24
25
26
27
28
public function AvoiderGame() 
{
	downKeyIsBeingPressed = false;
	upKeyIsBeingPressed = false;
	leftKeyIsBeingPressed = false;
	rightKeyIsBeingPressed = false;

onKeyPress event handler:

?View Code ACTIONSCRIPT3
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
public function onKeyPress( keyboardEvent:KeyboardEvent ):void
{
	if ( keyboardEvent.keyCode == Keyboard.DOWN )
	{
		downKeyIsBeingPressed = true;
	}
	else if ( keyboardEvent.keyCode == Keyboard.UP )
	{
		upKeyIsBeingPressed = true;
	}
	else if ( keyboardEvent.keyCode == Keyboard.LEFT )
	{
		leftKeyIsBeingPressed = true;
	}
	else if ( keyboardEvent.keyCode == Keyboard.RIGHT )
	{
		rightKeyIsBeingPressed = true;
	}
}

I’ll leave you to figure out the onKeyRelease event handler :)

Perhaps you are thinking that I am a bit of a hypocrite, because back in Part 2 I said that it was bad to copy and paste code like that. Fair point! Actually, it would be much better to use an array of Booleans to store the states of every single key. I encourage you to have a go at that yourself (I did leave some hints in the comments of one of the earlier parts, if you get stuck). Alternatively, there’s a great piece of code here that’ll handle it all for you, the credit for which goes to the ever-excellent senocular. (If you’re having trouble getting this code to work, Monkeyman has written a great comment explaining what to do here.)

Now, I don’t fancy having four functions, moveDownABit(), moveUpABit(), etc., so I’m going to suggest we generalise moveDownABit(), just as we did with the Enemy code.

Go back to Avatar.as and change your moveDownABit() function to this:

?View Code ACTIONSCRIPT3
11
12
13
14
15
public function moveABit( xDistance:Number, yDistance:Number ):void
{
	x += xDistance;
	y += yDistance;
}

We now need to pass the directions through from AvoiderGame.as, so reopen that file. Start by changing what we already had, from this:

?View Code ACTIONSCRIPT3
109
110
111
112
if ( downKeyIsBeingPressed )
{
	avatar.moveDownABit();
}

to this:

?View Code ACTIONSCRIPT3
109
110
111
112
if ( downKeyIsBeingPressed )
{
	avatar.moveABit( 0, 3 );
}

That’ll work, but we shouldn’t be writing “3″ explicitely, should we? Try this instead:

?View Code ACTIONSCRIPT3
109
110
111
112
if ( downKeyIsBeingPressed )
{
	avatar.moveABit( 0, 1 );
}

And in Avatar.as:

?View Code ACTIONSCRIPT3
11
12
13
14
15
16
public function moveABit( xDistance:Number, yDistance:Number ):void
{
	var baseSpeed:Number = 3;
	x += ( xDistance * baseSpeed );
	y += ( yDistance * baseSpeed );
}

See what we’re doing? If not, let’s add in the other three directions and make it a bit clearer. Go back to AvoiderGame.as:

?View Code ACTIONSCRIPT3
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
if ( downKeyIsBeingPressed )
{
	avatar.moveABit( 0, 1 );
}
else if ( upKeyIsBeingPressed )
{
	avatar.moveABit( 0, -1 );
}
else if ( leftKeyIsBeingPressed )
{
	avatar.moveABit( -1, 0 );
}
else if ( rightKeyIsBeingPressed )
{
	avatar.moveABit( 1, 0 );
}

You see, the two parameters now define the direction of movement, rather than the speed. Speed is defined inside the Avatar class, which makes sense (and also follows the standard set by the Enemy class).

However, you can affect the relative speed of the avatar from the AvoiderGame class. For example, you could make WSAD the “walking” controls, which make the avatar move at half the speed, simply by adding lines like this:

?View Code ACTIONSCRIPT3
if ( sKeyIsBeingPressed )
{
	avatar.moveABit( 0, 0.5 );
}

Setting Some Boundaries

If you run the game now, you’ll see that it’s possible to move the avatar outside of the playing field, and therefore avoid all the enemies entirely!

To get round this, all we have to do is write some code to check, every tick, whether the avatar somewhere it shouldn’t be, and move it back into the playing field if so.

I think the best place for this is in AvoiderGame.as, in the onTick() function, immediately after all the code we’ve just written. Let me demonstrate with this code snippet:

?View Code ACTIONSCRIPT3
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
if ( useMouseControl )
{
	avatar.x = mouseX;
	avatar.y = mouseY;
}
else
{
	if ( downKeyIsBeingPressed )
	{
		avatar.moveABit( 0, 1 );
	}
	else if ( upKeyIsBeingPressed )
	{
		avatar.moveABit( 0, -1 );
	}
	else if ( leftKeyIsBeingPressed )
	{
		avatar.moveABit( -1, 0 );
	}
	else if ( rightKeyIsBeingPressed )
	{
		avatar.moveABit( 1, 0 );
	}
}
 
//we should check the avatar's position here
//and move it if we need to
 
var avatarHasBeenHit:Boolean = false;
for each ( var enemy:Enemy in army ) 
{
	enemy.moveABit();

Where isn’t the avatar allowed? Let’s start with horizontal movement: the avatar can’t go left of the playing field. Now, we know that the left side of the play screen has an x-coordinate of zero (check the diagram in the first part of this tutorial for a reminder). So if the avatar’s x-coordinate is less than zero, it must be to the left of the play screen:

?View Code ACTIONSCRIPT3
139
140
141
142
if ( avatar.x < 0 )
{
	//avatar is left of play screen
}

What shall we do about it? It’s pretty simple — we just move the avatar back to the left edge:

?View Code ACTIONSCRIPT3
139
140
141
142
if ( avatar.x < 0 )
{
	avatar.x = 0;
}

Actually, because the avatar’s registration point is right in its centre, half-way across its width, this will let the avatar get half-way out of the screen:

screenshot

You can keep it like this if you like, but I’d rather make sure the whole avatar was inside the screen. Fortunately this is easy too; we can just change our code like this:

?View Code ACTIONSCRIPT3
139
140
141
142
if ( avatar.x < ( avatar.width / 2 ) )
{
	avatar.x = avatar.width / 2;
}

If that’s confusing, just bear in mind that avatar.x refers to the x-coordinate of the centre of the avatar. The centre is, naturally, half a width away from the left-hand side. (Of course, if your avatar’s registration point isn’t centred, you’ll have to measure the distance yourself.)

This makes the avatar stop at the left side of the screen as if it were a wall:

screenshot

The other three directions can be added similarly:

?View Code ACTIONSCRIPT3
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
if ( avatar.x < ( avatar.width / 2 ) )
{
	avatar.x = avatar.width / 2;
}
if ( avatar.x > 400 - ( avatar.width / 2 ) )
{
	avatar.x = 400 - ( avatar.width / 2 );
}
if ( avatar.y < ( avatar.height / 2 ) )
{
	avatar.y = avatar.height / 2;
}
if ( avatar.y > 300 - ( avatar.height / 2 ) )
{
	avatar.y = 300 - ( avatar.height / 2 );
}

The numbers 400 and 300 are the width and height of my game. Bonus points for you if you use the soft-coding tip I talked about in Part 6 instead ;)

Challenges

We’re getting to the point now where you understand a lot of concepts, and it’s actually more beneficial for you to have a go at using them yourself rather than having someone else tell you every single step to take. If you’re lacking inspiration, here are some ideas to get you started.

How about changing the menu screen to have two buttons — one for mouse control, and one for keyboard control? Hint: pass the value of useMouseControl through the AvoiderGame() constructor, like we do with the enemy’s position in the Enemy() constructor.

You might have found that the game is considerably harder with keyboard controls than with mouse controls. How about altering the rate at which enemies appear if the player is using the keyboard?

You’ve got four-way movement sorted; now how about eight-way movement? Hint: you can check if ( ( downKeyIsPressed ) && ( leftKeyIsPressed ) ). Be careful — the Pythagorean Theorem tells us that if you move three pixels down and three pixels left within a single tick, you’ll travel further (and therefore faster) than if you just move three pixels down or three pixels left. How can you get around that? UPDATE: Rasmus Wriedt Larsen wrote a tutorial on doing exactly this. Check it out here.

It’s always irritating to have to switch between keyboard and mouse. Can you make it so that the Start and Restart buttons can be pressed by hitting the space bar?

Don’t forget to have a go at making an array of booleans to hold the state of all your keys at once, or to use senocular’s KeyboardObject class, as mentioned above.

If you’d like a hand with any of these challenges, stick a note in the comments box below. If you’ve got any of your own, I’d love to hear them :)

Wrapping Up

That’s it for this week. As usual you can grab the zip file here.

In Part 8, we’ll add a preloader, an essential requirement for any Flash game.

{ 136 comments… read them below or add one }

brainz88 November 7, 2009 at 11:55 am

hi. .. again

well , there’s some question i wanna ask

In menuScreen i let player choose what they wanna use , if they press the space bar ,they’ll use keyboard , if they click the startButton they’ll use mouse

when game over , they’ll just have to click restart to back to menuScreen ,and choose what they want to use

so i made the code like this

(DocumenClass.as)

public function DocumentClass()
{
     menuScreen = new MenuScreen();
     menuScreen.addEventListener(NavigationEvent.START , onRequestStartWithMouse);
     menuScreen.addEventListener(NavigationEvent.STARTKB, onRequestStartWithKeyboard);
     menuScreen.x = 0;
     menuScreen.y = 0;
     addChild(menuScreen);

     stage.stageFocusRect = false;
     stage.focus = menuScreen;          

}

public function onRequestStartWithMouse(event:NavigationEvent):void { start(true); } public function onRequestStartWithKeyboard(event:NavigationEvent):void { trace("whyBecomeFalse?"); //start(false); } public function onRequestRestart(event:NavigationEvent):void { reStart(); } public function start(chooseMouse:Boolean):void {
var useMouse:Boolean = chooseMouse; playScreen = new AvoiderGame(useMouse); playScreen.addEventListener(AvatarEvent.DEAD , onAvatarDeath); playScreen.x = 0; playScreen.y = 0; addChild(playScreen); removeChild(menuScreen); menuScreen = null; stage.focus = playScreen; }

public function reStart():void { menuScreen = new MenuScreen(); menuScreen.addEventListener(NavigationEvent.START_M , onRequestStartWithMouse); menuScreen.addEventListener(NavigationEvent.START_KB, onRequestStartWithKeyboard); menuScreen.x = 0; menuScreen.y = 0; addChild(menuScreen); removeChild(gameOverScreen); gameOverScreen = null; stage.focus = menuScreen; }

(MenuScreen.as)

public function MenuScreen()
{
    addEventListener(Event.ADDED_TO_STAGE , onAddedToStage2);
    startButton.addEventListener(MouseEvent.CLICK , onClickStart);
}

public function onClickStart(event:MouseEvent):void { dispatchEvent( new NavigationEvent(NavigationEvent.START_M)); }

public function onAddedToStage2(event:Event):void { stage.addEventListener(KeyboardEvent.KEY_DOWN , onKeyDown); }

public function onKeyDown(event:KeyboardEvent):void { switch(event.keyCode) { case Keyboard.SPACE : trace("spaceBarPressed");
//dispatchEvent(new NavigationEvent(NavigationEvent.START_KB)); break;

    default:
    break;
 }

}

i put trace (“whyBecomeFalse”) and trace(“spaceBarPressed”) ,

there’s 2 problem i don’t understand

1st : i clicked the startButton on menuScreen ,which will play with mouse ,

but flash still display whyBecomeFalse in Output panel ,even if i didn’t declare

dispatchEvent for NavigationEvent.START_KB

why function onRequestStartWithKeyboard() can be called ?

2nd : why in playScreen and gameOverScreen ,spaceBarPressed still can be detected ?

thx and sorry for my broken english

Michael Williams November 7, 2009 at 12:16 pm

Hey brainz88,

That is very peculiar.

I noticed that you use NavigationEvent.START and NavigationEvent.STARTKB in one part of your code, but NavigationEvent.START_M and NavigationEvent.START_KB in another part — perhaps this has something to do with it? What does your NavigationEvent class look like?

As for your second question, I think that’ll be answered for you in Part 12 :)

brainz88 November 7, 2009 at 4:56 pm

sorry mistyped it should be

public function DocumentClass()
{
     menuScreen = new MenuScreen();
     menuScreen.addEventListener(NavigationEvent.START_M , onRequestStartWithMouse);
     menuScreen.addEventListener(NavigationEvent.START_KB, onRequestStartWithKeyboard);
      ... ... 
} 

oopss .. coincidence solve 1st problem , but don’t know the logic why can coz bug

my “NavigationEvent.as” , was like this

public class NavigationEvent extends Event
    {
public static const RESTART:String=" "; public static const START_M:String=" "; public static const START_KB:String=" "; public function NavigationEvent(type:String) { super(type); } }

after i changed to this

public class NavigationEvent extends Event
    {
public static const RESTART:String="restart"; public static const START_M:String="start_m"; public static const START_KB:String="start_kb"; public function NavigationEvent(type:String) { super(type); } }

it works

for the 2nd problem , i’ve already used stage.focus , but “spaceBarPressed” still can be detected in playScreen and gameOverScreen

Michael Williams November 7, 2009 at 5:18 pm

Ahh OK that explains it. Flash uses the actual value of those String constants to figure out which event is being dispatched. Since all three had the same value, Flash was detecting all three events!

Yeah, if you do stage.addEventListener() for the keyboard listeners in your screens, then they will be able to detect the space bar being pressed (until the garbage collection kicks in). The best thing to do is run stage.removeEventListener() on the keyboard listeners in those screens right before you removeChild() them.

Julien November 8, 2009 at 2:18 am

Hello Michael. First a Big Big Thank for making AS3 and programing stuff accessible even for completly beginner person as I am. I’ve fastly checked around and yours tutorials are the best I have seen so far and the only ones really “understandable” for a slob like me.

I’ve worked like a mad these three last days and I’m on a point where my Avoidergame begin to look like a real thing with collect of different weapon / lives / shoots etc. and that’s thanks to you. I need your help if possible, just to understand one point that completly block me from now…: I can’t see the way to fix the strange bug I (and you also, – according to the version presented on the top of this page) have… Check this again on this page please and you’ll notice that during playing with Keybord if you click on Mouse, the code stop listening keybord and the avatar freeze ..?. I know eventListener are on stage and I know I need to put something behind the playScreen to avoid this, but I really can’t fix it that way. Even If I click on the Clock or if I click on an enemy, I can’t come back or stay on Stage and my keybord isn’t listened any more.

Big thank again Michael. ++

brainz88 November 8, 2009 at 12:47 pm

yess.. thx , it works

time to learn part 8

thx again

Michael Williams November 10, 2009 at 11:24 am

@brainz88: Great!

@Julien: Thanks :) I’m glad you’ve found it helpful.

Huh, you’re right — I never noticed that before! Well spotted. The problem does get fixed in Part 12, though, and it actually only takes a couple lines of code. So hopefully that should solve the problem you’re having, too.

Let me know when your game’s done, I’d like to see it :)

Julien November 10, 2009 at 1:12 pm

Hello Michael

Thanks for your fast reply. Your doing an amazing job here ! Once again you were right, this bug is just fixed when the “focus” is set properly AND the Listeners hold thier “(false, 0, true)” property. I guess it just came from a complex story of trigger order from the different Listener.

I follow the construction of my game and I’ll show you when It’ll be ok for sure. Thank again, for learning us good base of AS3. This is so cool !! ++

Michael Williams November 10, 2009 at 1:21 pm

Hey Julien,

Great! Glad it’s working :) Looking forward to seeing your game.

Monkeyman November 15, 2009 at 6:03 pm

First off, I just want to say, great tutorials! Even though nearly every other comment says that, I just had to anyway :)

I also want to point something out about senocular’s code. It’s amazingly helpful, but I encountered a problem when I tried to use it. I fixed it, but I’m just pointing this out in case anyone else has the same problem if they try to use it.

For some reason, I’m not sure why, this does not work:

var key:KeyObject = new KeyObject(stage);
var xSpeed:Number = 0;
var ySpeed:Number = 0;
if ( key.isDown(key.DOWN))
{
       ySpeed ++;
}
if ( key.isDown(key.UP) )
{
    ySpeed --;
}
if ( key.isDown(key.LEFT ))
{
    xSpeed --;
}
if ( key.isDown(key.RIGHT) )
{
    xSpeed ++;
}
if(xSpeed!=0 && ySpeed!=0)
{
    xSpeed = (xSpeed * Math.sqrt(2))/2;
    ySpeed = (ySpeed * Math.sqrt(2))/2;
}
avatar.moveABit(xSpeed, ySpeed);

This is in my AvoiderGame.as file. Now, this is what senocular says to do, but it doesn’t work. Next, I tried to declare this as a public variable at the beginning of my AvoiderGame class, but that didn’t work either, and I’m pretty sure it’s because I hadn’t changed the focus of my stage to the playScreen yet, so calling KeyObject(stage) was called while menuScreen was still the stage.focus.

Anyway, if anyone else has encountered this problem, you have to do this: In AvoiderGame.as:

public var key:KeyObject;

public function AvoiderGame(x:Boolean) 
{

    addEventListener(Event.ADDED_TO_STAGE, onAddToStage);
 ...
}
public function onAddToStage(event:Event):void
{
    key = new KeyObject(stage);
}

And that should fix the problem!

Michael Williams November 16, 2009 at 12:36 pm

Thanks, Monkeyman, it’s always great to hear that :)

Wow, great tip — nice work on diagnosing and fixing it, and cheers for sharing! I’ve put a link to your comment in the main tutorial.

Kit November 30, 2009 at 3:35 pm

Hi there,

so far I’ve been doing pretty good in the tutorium, usually able to figure out errors or little changes on my own.

I added a controlScreen between menuScreen and playScreen which lets you choose via button which kind of controls you prefer. I changed the constructor of AvoiderGame(controls:Boolean) => if you want mouse controls, you start the game with new AvoiderGame(true), if you want keyboard control its new AvoiderGame(false). So far so good, works on the first run-through.

when you hit restart you get back to the menuScreen, thus you can choose again which kind of control scheme you like before playing for the second time.

But: the keyboard controls do not work on the second time. no matter what key you press, on the second play-through the avatar only moves up. (upon key release, it stops moving.) this happens with first play-trough as mouse or with first play-through as keyboard.

What I changed was that you can use either wasd or the arrows for controls:

public function onKeyPress(keyboardEvent:KeyboardEvent):void{

        if ((keyboardEvent.keyCode == Keyboard.DOWN) || (keyboardEvent.keyCode == 83)){
            downKeyIsBeingPressed = true;
        }

        if ((keyboardEvent.keyCode == Keyboard.LEFT) || (keyboardEvent.keyCode == 65)){
            leftKeyIsBeingPressed = true;
        }

        if ((keyboardEvent.keyCode == Keyboard.RIGHT) || (keyboardEvent.keyCode == 68)){
            rightKeyIsBeingPressed = true;
        }

        if ((keyboardEvent.keyCode == Keyboard.UP) ||  (keyboardEvent.keyCode = 87)){
            upKeyIsBeingPressed = true;
        }

    }

do you have any idea what the problem might be?

ps: i love this tutorial! it is easily the best on the web for getting to know AS3. especially if you never heard of OOP.

Michael Williams November 30, 2009 at 7:41 pm

Hey Kit, thanks for the kind words.

It looks like there’s nothing wrong with your code (and your method of passing a boolean into the constructor is great too). I think the problem may actually be something to do with the way Flash handles keyboard focus, which I explain in more detail in Part 12. If you get to that part and the problem’s still there, please let me know and I’ll look into it :)

Josh December 25, 2009 at 12:27 am

why is this else not expected?

avatar = new Avatar ();
            addChild( avatar );
            if( useMouseControl );
            {
                avatar.x = mouseX;
                avatar.y = mouseY;
            }
            else
            {
                avatar.x = 200;
                avatar.y = 250;
            }

Michael Williams December 29, 2009 at 12:35 am

It’s because of the semi-colon after the if( useMouseControl ). Flash basically treats it as though you typed an empty pair of curly braces.

noobmaster December 31, 2009 at 2:29 pm

Hint: pass the value of useMouseControl through the AvoiderGame() constructor, like we do with the enemy’s position in the Enemy() constructor.

I wonder if it would have any negative effects if we instead accessed the value of useMouseControls from the DocumentClass?

public function onRequestStartKey(navigationEvent:NavigationEvent)
        {
            playScreen = new AvoiderGame();
            playScreen.useMouseControls = false;
            playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
            playScreen.x = 0;
            playScreen.y = 0;
            addChild( playScreen );

        howGameStarted = "keyboard"
        menuScreen = null;
    }

Or would it cluster too much the DocumentClass?

edit: oh, wait. That’s the same thing no? :P

Michael Williams January 1, 2010 at 6:58 pm

@noobmaster: nice solution! That’s a neat way of doing it, setting the value after you’ve created the play screen. More flexible if you want to alter things later. Well done :)

Wesley February 7, 2010 at 5:38 am

How do you integrate Senocular’s code in to the game?

Michael Williams February 9, 2010 at 7:40 pm

@Wesley: that’s a challenge for you ;) What’ve you tried so far?

Alon Ski February 9, 2010 at 8:31 pm

Hey I got a challenge for you. Instead of making two buttons one for keyboard and one for mouse. How about making one button that switches between keyboard and mouse and also updates the text on the button? Will I spent at least 4 hours on this very task and here it is: http://megaswf.com/view/5f3579df6a90667a4ea397848bcca806.html

What do you think?

Michael Williams February 9, 2010 at 8:42 pm

@Alon Ski: Nice! Very nice indeed :D

Are you going to implement it into your game now? I’d love to see that!

Alon Ski February 10, 2010 at 12:00 am

It actually is in my game. I will put a link to it once I am done with all of the parts. I just finished Part 8. Something annoying that I did was that when I was starting Part 8 I forgot to save my files from Part 7. I have saved all the parts up till then and its annoying to see the files: Game 1, Game 2…, Game 6, Game 8 =(

Anyways I will post once I am done. P.S. Do you have any multiplayer tutorials that I can look at? I have a plan for a multiplayer game and have done some research but so far I can’t figure out what to do really… Haven’t been learning Flash for more than a week and already I try to do something as complicated as online connections… Anyways…

Oh and another thing… Would you mind if I used this as a base to make a Flash Tutorial similar to the Shootorials on Kongregate? I would link back to every post through the game. Seems like a fun thing to do to help me remember what I did to make this simple game.

Nik February 10, 2010 at 10:07 am

 
package
{
    import flash.display.MovieClip;
    import flash.display.SimpleButton;
    import flash.events.MouseEvent;
    import flash.ui.Mouse;
    import flash.events.KeyboardEvent;
    import flash.ui.Keyboard;
    import flash.events.Event;

public class MenuScreen extends MovieClip { public var spaceKeyIsBeingPressed:Boolean; public function onAddToStage( event:Event ):void { stage.addEventListener( KeyboardEvent.KEY_DOWN, onKeyPress ); stage.addEventListener( KeyboardEvent.KEY_UP, onKeyRelease ); } public function MenuScreen() { spaceKeyIsBeingPressed = false; Mouse.show(); startButton.addEventListener( MouseEvent.CLICK, onClickStart ); if ( spaceKeyIsBeingPressed = true); { startButton.addEventListener( KeyboardEvent.KEY_DOWN, onPressStart ); } } public function onKeyPress( keyboardEvent:KeyboardEvent ):void { if ( keyboardEvent.keyCode == Keyboard.SPACE ) { spaceKeyIsBeingPressed = true; } } public function onKeyRelease( keyboardEvent:KeyboardEvent ):void { if ( keyboardEvent.keyCode == Keyboard.SPACE ) { spaceKeyIsBeingPressed = false; } } public function onClickStart( event:MouseEvent ):void { dispatchEvent( new NavigationEvent( NavigationEvent.START ) ); } public function onPressStart( event:KeyboardEvent):void { dispatchEvent( new NavigationEvent( NavigationEvent.START ) ); } }

}

i did this” the Start and Restart buttons can be pressed by hitting the space bar”, but fail. the it doesn’t work on main menu, and everytime i press a key, when the game is played it restart the game.

sorry, new to AS3. thanks

Michael Williams February 13, 2010 at 2:02 am

@Alon Ski: Ah, how annoying. Try using Git instead of manually backing everything up ;)

I’ve not done anything with multiplayer yet. Hmm… you could check out Come2Play.

Sure thing, feel free to use this as a base :) Let me know when you’re done, I’d love to see it.

@Nik: How about moving this line:

dispatchEvent( new NavigationEvent( NavigationEvent.START ) );

…to here:

    public function onKeyPress( keyboardEvent:KeyboardEvent ):void
    {
        if ( keyboardEvent.keyCode == Keyboard.SPACE )
        {
            //put it here
        }
    }

That way, as soon as the player hits space, it’ll dispatch the event to start the game :) Make sense?

Alon Ski February 13, 2010 at 11:04 am

Git is pretty cool thanks =D

Michael Williams February 17, 2010 at 11:12 am

Glad you like it :)

Nik February 22, 2010 at 2:32 am

already moved the line

public function MenuScreen() 
        {
            spaceKeyIsBeingPressed = false;
            Mouse.show();
            startButton.addEventListener( MouseEvent.CLICK, onClickStart );
            if ( spaceKeyIsBeingPressed = true);
                {
                    startButton.addEventListener( KeyboardEvent.KEY_DOWN, onKeyPress );
                }
        }
        public function onKeyPress( keyboardEvent:KeyboardEvent ):void
        {
            if ( keyboardEvent.keyCode == Keyboard.SPACE )
            {
                spaceKeyIsBeingPressed = true;
                dispatchEvent( new NavigationEvent( NavigationEvent.START ) );
            }
        }

but the space button still do nothing…

Michael Williams February 22, 2010 at 9:27 pm

Weird. Do you have an event listener added to the menu screen, listening for a NavigationEvent.START? Does it get triggered?

Nik February 23, 2010 at 2:07 am

here’s the full code

package 
{
    import flash.display.MovieClip;
    import flash.display.SimpleButton;
    import flash.events.MouseEvent;
    import flash.ui.Mouse;
    import flash.events.KeyboardEvent;
    import flash.ui.Keyboard;
    import flash.events.Event;

public class MenuScreen extends MovieClip 
{
    public var spaceKeyIsBeingPressed:Boolean;
    public var startButton:SimpleButton;
    public function onAddToStage( event:Event ):void
    {
        stage.addEventListener( KeyboardEvent.KEY_DOWN, onKeyPress );
        stage.addEventListener( KeyboardEvent.KEY_UP, onKeyRelease );
    }
    public function MenuScreen() 
    {
        spaceKeyIsBeingPressed = false;
        Mouse.show();
        startButton.addEventListener( MouseEvent.CLICK, onClickStart );
        startButton.addEventListener( KeyboardEvent.KEY_DOWN, onKeyPress );
        if ( spaceKeyIsBeingPressed = true);
            {
                startButton.addEventListener( KeyboardEvent.KEY_DOWN, onKeyPress );
            }
    }
    public function onKeyPress( keyboardEvent:KeyboardEvent ):void
    {
        if ( keyboardEvent.keyCode == Keyboard.SPACE )
        {
            spaceKeyIsBeingPressed = true;
            dispatchEvent( new NavigationEvent( NavigationEvent.START ) );
        }
    }
    public function onKeyRelease( keyboardEvent:KeyboardEvent ):void
    {
        if ( keyboardEvent.keyCode == Keyboard.SPACE )
        {
            spaceKeyIsBeingPressed = false;

        }
    }
    public function onClickStart( event:MouseEvent ):void
    {
        dispatchEvent( new NavigationEvent( NavigationEvent.START ) );
    }
    public function onPressStart( event:KeyboardEvent):void
    {

    }
}

}

sorry for the trouble, after some modification still cannot get it to work…

Michael Williams February 23, 2010 at 2:23 am

You can delete

            if ( spaceKeyIsBeingPressed = true);
                {
                    startButton.addEventListener( KeyboardEvent.KEY_DOWN, onKeyPress );
                }

from the constructor, by the way. (Can you see why?)

What you need to do is find out where exactly this is going wrong. So, is the onKeyPress() function being called? If so, is it getting inside that if-statement? Is it dispatching the event? Is something else listening to the event? And so on.

You can use the trace() statement for this — for example:

        public function onKeyRelease( keyboardEvent:KeyboardEvent ):void
        {
            trace( "onKeyRelease() is being called" );
            if ( keyboardEvent.keyCode == Keyboard.SPACE )
            {
                spaceKeyIsBeingPressed = false;
} }

Find out where it’s going wrong, and you’ll be a lot closer to fixing it. See my debugging guide for more.

Nik February 23, 2010 at 3:24 am

it works! (almost)

public function MenuScreen() 
        {
            spaceKeyIsBeingPressed = false;
            Mouse.show();
            startButton.addEventListener( MouseEvent.CLICK, onClickStart );
            startButton.addEventListener( KeyboardEvent.KEY_DOWN, onKeyPress );
            addEventListener(Event.ENTER_FRAME, onAddToStage, false, 0, true); //i add this
                        }

i delete the line you mention, & realize there’s no event listener for the “onAddToStage”. so now i can start the game with spacebar (even ingame!!). with this error:

TypeError: Error #1009: Cannot access a property or method of a null object reference.
    at MenuScreen/onAddToStage()

it’s like it’s keep calling the event every frame…

Michael Williams February 23, 2010 at 3:34 am

Nice work!

OK, an error #1009 means that you are trying to access a function or a variable of an object that does not yet exist (or that has been set to null). So next you have to figure out which object that is. Use the debugging guide to help.

Nik February 23, 2010 at 4:19 am

i use your debugging guide & come out with this to trace:

public function MenuScreen() 
        {
            trace( "a" );
            spaceKeyIsBeingPressed = false;
            Mouse.show();
            startButton.addEventListener( MouseEvent.CLICK, onClickStart );
            startButton.addEventListener( KeyboardEvent.KEY_DOWN, onKeyPress );
            addEventListener(Event.ENTER_FRAME, onAddToStage, false, 0, true);
            trace( "b" );
            }
        public function onAddToStage( event:Event ):void
        {
            trace( "onAddToStage" );
            trace( "KeyboardEvent.KEY_DOWN, onKeyPress"  );
            trace( "KeyboardEvent.KEY_DOWN, onKeyPress" );
            trace( onKeyPress );
            trace( onKeyRelease );
            trace( "c" );
            stage.addEventListener( KeyboardEvent.KEY_DOWN, onKeyPress );
            trace( "c2" )
            stage.addEventListener( KeyboardEvent.KEY_UP, onKeyRelease );
            trace( "d" );
        }
        public function onKeyPress( keyboardEvent:KeyboardEvent ):void
        {
            trace( "e" );
            trace( "onKeyPress() is being called" );
            if ( keyboardEvent.keyCode == Keyboard.SPACE )
            {
                trace( "f" );
                spaceKeyIsBeingPressed = true;
                dispatchEvent( new NavigationEvent( NavigationEvent.START ) );
                trace( "g" );
            }
            trace( "g2" );
        }
        public function onKeyRelease( keyboardEvent:KeyboardEvent ):void
        {
            trace( "h" );
            trace( "onKeyRelease() is being called" );
            if ( keyboardEvent.keyCode == Keyboard.SPACE )
            {
                trace( "i" );
                spaceKeyIsBeingPressed = false;
                trace( "j" );

        }
    }

output:

a
b
onAddToStage
KeyboardEvent.KEY_DOWN, onKeyPress
KeyboardEvent.KEY_DOWN, onKeyPress
function Function() {}
function Function() {}
c
c2
d
e
onKeyPress() is being called
f
g
g2
onAddToStage
KeyboardEvent.KEY_DOWN, onKeyPress
KeyboardEvent.KEY_DOWN, onKeyPress
function Function() {}
function Function() {}
c
TypeError: Error #1009: Cannot access a property or method of a null object reference.
    at MenuScreen/onAddToStage()
onAddToStage
KeyboardEvent.KEY_DOWN, onKeyPress
KeyboardEvent.KEY_DOWN, onKeyPress
function Function() {}
function Function() {}
c
TypeError: Error #1009: Cannot access a property or method of a null object reference.
    at MenuScreen/onAddToStage()
h
onKeyRelease() is being called
i
j
onAddToStage
KeyboardEvent.KEY_DOWN, onKeyPress
KeyboardEvent.KEY_DOWN, onKeyPress
function Function() {}
function Function() {}
c
TypeError: Error #1009: Cannot access a property or method of a null object reference.
    at MenuScreen/onAddToStage()

the problem start after “trace (c);”, but i cannot figured a way to fix it

Epic428 March 3, 2010 at 4:43 am

So i noticed that Weasley up above had inquired about implementing senocular’s code. Haha I am wondering the same exact thing. I also noticed he never replied back. Anyway I liked they way Monkeyman created his code. It seemed that it reduced the amount of code originally used with the tutorial and i am a personal fan of making things much more efficient while remaining effective. I made the attempt to implement the code but, alas, i realized i have no clue how to use senocular’s code.

So far I have tried downloading the class and adding it to my classes folder thinking that would work. unfortunately it did not. I tried google searching how to implement it and unfortunately i hadn’t any luck. in fact googling “integrating senocular’s code” returned this page as top result but thats beside the point lol. anyway I did come across other forums where people were using some of his code and they were importing the classes. So i thought to try it using my first approach as well but again to no avail. What exactly am i missing with this? am i supposed to download more than just that page’s source code and add them to my flash directories somehow? Any help or helpful hints on the direction to go would be much appreciated. Thank you for these tutorials the amount of information i have learned so far is on one hand overwhelming but on the other its easy to follow and easy to understand.

Epic428´s last blog: School Gone Too Far – Spies on Students Through Webcams

Michael Williams March 6, 2010 at 12:07 am

@Nik: Good work so far. The error #1009 is trying to tell you that either stage, KeyboardEvent or onKeyPress is not set to anything yet. This time, it’s stage.

The reason for that is this line:

addEventListener(Event.ENTER_FRAME, onAddToStage, false, 0, true);

You’ve called your function onAddToStage, but you’re triggering it based on an ENTER_FRAME event — it should be a ADDED_TO_STAGE event :)

@Epic428: Sure thing!

Well, the first step is, you need to create an instance of the KeyObject class in your play screen class file somehow. If you copy and paste senocular’s code into a new AS file called KeyObject.as, that’ll let you do it.

…except no, it won’t. Something I never explained in the tutorial (whoops) is what it means when you have something written after package — for example, as here, package com.senocular.utils. It means that you need to put the class file in a directory structure like \com\senocular\utils\.

In other words, inside your AvoiderGame folder, you should have:

AvoiderGame\classes\com\senocular\utils\KeyObject.as

This will then let you type, import com.senocular.utils.KeyObject, in the same place as your other imports, which will finally let you type var keyObject:KeyObject = new keyObject(stage);.

Phew! That’s more complicated than I thought. Does it make sense?

Epic428 March 6, 2010 at 5:17 am

hrmm.. for some reason my last comment never showed up. anyway lol i did end up figuring out how to get it implemented. through extensive googling i came across a tutorial that talked about making custom packages and how to set up class paths for classes that are frequently used. after playing with that i finally figured out how to get it to work right. Actually going through the comments on one of your other parts i realized you actually explained how to accomplish this when someone else inquired about classes with useful or simple functions.

I do have a new problem that i cannot figure out though. I set up a button on my menu screen that when clicked it alternates between keyboard and mouse controls and tells you which controls are being used. My problem however is say for example i want to begin the game with mouse controls. by default keyboard is enabled so i switch them. then i decide after game over id like to change to keyboard controls so i go back to the menu screen. well when i go back to the menu screen it says keyboard is enabled but it actually isn’t. basically every time you go back to the menu screen from the game over screen the button loads it default text but the controls remain the same and u have to click the button twice to correct it. an example of this issue can be found here: http://www.epictechworld.com/testing-avoider-game-from-tutorial (PS: dont mind that its breaking the layout lol)

Epic428´s last blog: School Gone Too Far – Spies on Students Through Webcams

Leave a Comment

Writing code? Write <pre> at the start and </pre> at the end to keep it looking neat.

CommentLuv Enabled

Anti-Spam Protection by WP-SpamFree

Previous post:

Next post: