AS3 Avoider Game Tutorial, Part 12: Garbage Collection

by Michael James Williams on March 25, 2009 · 276 comments

in Avoider Game Base,Tutorial

So far, every programming concept we’ve covered has helped to add a new gameplay element. This part will be the exception to that rule; while the changes we’re about to make are very important for the creation of bigger, more complex games, they will not be obvious to the player.

In this, the final part of my AS3 and Flash CS3 conversion of Frozen Haddock’s avoider game tutorial, we’ll deal with “garbage collection”.

Click the image below to check it out — but you won’t notice a difference unless you play for a long time.

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.

What are Variables?

What happens when we type something like:

var score:Number = 100;

Intuitively, it’s like we create a box, write “score” on the lid, and put the number 100 inside it.

screenshot

That’s a pretty good way of thinking about it, and it’s true for Strings, Booleans, and all types of Numbers. What about this line:

var avatar:Avatar = new Avatar();

Presumably that does the same thing, creating a box, writing “avatar” on the lid, and making a new Avatar object to go inside? Actually, no. The box and the Avatar are kept separate, with the Avatar seemingly floating around in space, and the box contains directions to the actual Avatar object. It’s like the box is tied to the Avatar with a piece of rope:

screenshot

This is the case for any type of Object other than Strings, Booleans, and Numbers.

That’s interesting and all, but what does it actually mean? Well, consider this code:

var score:Number = 100;
var newScore:Number = score;

Here, the variable newScore will copy the contents of score into its own insides:

screenshot

But with this code:

var avatar:Avatar = new Avatar();
var newAvatar:Avatar = avatar;

the variable newAvatar will actually only copy the link, the so-called reference, to the same Avatar object — no new Avatar object is created or copied!

screenshot

This can cause confusion when trying to change something about a variable. For example, using the above code, if you wrote:

newScore = 250;
trace( "score is:", score );
trace( "newScore is:", newScore );

then the trace statements would output:

score is: 100
newScore is: 250

which is what you’d expect. The contents of the boxes are different, and can be altered separately.

screenshot

Now let’s try a similar thing with the avatars:

avatar.scaleY = 1;
newAvatar.scaleY = 2;
trace( "avatar.scaleY is:", avatar.scaleY );
trace( "newAvatar.scaleY is:", newAvatar.scaleY );

This will output:

avatar.scaleY is: 2
newAvatar.scaleY is: 2

This seems surprising, but actually makes perfect sense when you consider that there is only one Avatar object and that both avatar and newAvatar are referring to the same thing.

Incidentally, scaleY is used to stretch or squash an image vertically, so the situation would look like this:

screenshot

In earlier parts of this tutorial, I have said that setting an object equal to null essentially deletes them. This was a gross oversimplification. For Strings, Numbers, and Booleans it’s true:

score = null;

screenshot

…but for any other object, all setting it to null does is remove the link from the box (variable) to the actual object:

avatar = null;

screenshot

Here, the avatar variable is no longer linked to the actual Avatar object, so trying to trace avatar.scaleY will just result in an error. The Avatar object still exists, though, and the newAvatar variable is still linked to it, so tracing newAvatar.scaleY will return 2, as before.

This is useful, as it lets us create an object in one class or function and pass it to another class or function to do something else with, rather than messing around copying all the contents of an object and deleting the original. It does raise an interesting question, though: what happens if we now set newAvatar to null?

newAvatar = null;

screenshot

Now the Avatar is in no-man’s land; we’ve destroyed all our references to it and so can’t access it any more. And yet, it still exists, and it’s still taking up space in the player’s computer’s RAM, even though it’ll never be used again. This might not seem like a big deal, but when you consider that the same thing happens with all the enemies, the background music, the sound effects, the menu screen… it adds up, and even a simple game like ours would eventually fill up the user’s computer with these “dead objects”, making it very slow.

Or at least, it would, if not for the Flash garbage collector.

Garbage Collection in Flash

The Flash Player has a built-in tool to clear these dead objects. Every so often — I hate to use such a vague phrase, but we just don’t know when exactly this will take place — the “garbage collector” checks to see if there are any objects that don’t have any variables linking to them. If so, they get removed.

Additionally, if a variable does link to an object, but that variable itself is contained in another object that isn’t linked to by another variable, it’ll get removed. For instance, in our game, avatar belongs to the playScreen, which in turn belongs to the document class:

screenshot

If we now remove the reference from the document class to the play screen:

playScreen = null;

screenshot

…then both the playScreen and the avatar objects will be garbage collected.

And this can be extended even further. Any object that does not have a direct or indirect chain of links from the document class (or stage) will be garbage collected. (For much more information on exactly how this works, check out Grant Skinner’s excellent series of posts.)

Aside from setting variables, links are also created with addChild. Let’s take a look at this. Run your game, and stretch the window so that it’s taller than it should be:

screenshot

screenshot

Start playing. When you click the start button, remember this code is run:

?View Code ACTIONSCRIPT3
62
63
64
65
66
67
68
69
70
71
public function onRequestStart( navigationEvent:NavigationEvent ):void
{
	playScreen = new AvoiderGame();
	playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
	playScreen.x = 0;
	playScreen.y = 0;
	addChild( playScreen );
 
	menuScreen = null;
}

menuScreen is set to null, and yet:

screenshot

We can still see a bit of it! It could be that the garbage collector just hasn’t been run yet, but that’s not the case here (feel free to test it by playing for a while though ;) ). What’s happening is, the menuScreen still has a link from the document class, since it was addChild-ed. To remove this link, we need to use the (unsurprisingly named) removeChild function:

?View Code ACTIONSCRIPT3
62
63
64
65
66
67
68
69
70
71
72
public function onRequestStart( navigationEvent:NavigationEvent ):void
{
	playScreen = new AvoiderGame();
	playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
	playScreen.x = 0;
	playScreen.y = 0;
	addChild( playScreen );
 
	removeChild( menuScreen );
	menuScreen = null;
}

screenshot

That’s better. Except, no, it’s not, because we’ve lost keyboard control. What the heck?

Clicking on the game lets us control it again, and that gives us a hint for the cause of the problem. In Part 7 we found that the special stage object was what we used to actually listen to the keyboard. More specifically, the stage contains a reference to yet another object which has keyboard focus; this object passes the keys pressed through to the stage, which is why we can always use it to listen to the keyboard events.

(Imagine we made an application in Flash that let you type your name, email address, and website URL into different text input boxes. At any one time, only one of the boxes has the “focus”; when you type something into the “name” box, it doesn’t affect the email or URL box. However, each of them lets the stage know what you’re typing.)

When we clicked the button to start the game, we gave it the focus — and then we removed it from the stage so that it couldn’t pass information about the keys being pressed back to the stage any more. D’oh.

This is easy to fix, though! We can just tell the stage that the play screen now has the focus:

?View Code ACTIONSCRIPT3
62
63
64
65
66
67
68
69
70
71
72
73
74
public function onRequestStart( navigationEvent:NavigationEvent ):void
{
	playScreen = new AvoiderGame();
	playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
	playScreen.x = 0;
	playScreen.y = 0;
	addChild( playScreen );
 
	removeChild( menuScreen );
	menuScreen = null;
 
	stage.focus = playScreen;
}

Only trouble is, we end up with this ugly yellow border around the game:

screenshot

It’s because the stage has an option to highlight the object that currently has the focus. This might be useful in certain circumstances, but we don’t need it here. It can be turned off with one line; just add this to the document class:

?View Code ACTIONSCRIPT3
24
stage.stageFocusRect = false;

screenshot

Now, let’s apply these ideas throughout the document class. I’ve collapsed this code box so that it doesn’t take up lots of room; click the little triangle to expand it:

?View Code ACTIONSCRIPT3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package 
{
	//Avoider Game Tutorial, by Michael James Williams
	//http://gamedev.michaeljameswilliams.com
 
	import flash.display.MovieClip;
	import flash.events.Event;
	import flash.events.ProgressEvent;
	public class DocumentClass extends MovieClip 
	{
		public var menuScreen:MenuScreen;
		public var playScreen:AvoiderGame;
		public var gameOverScreen:GameOverScreen;
		public var loadingProgress:LoadingProgress;
 
		public function DocumentClass() 
		{
			loadingProgress = new LoadingProgress();
			loadingProgress.x = 200;
			loadingProgress.y = 150;
			addChild( loadingProgress );
			loaderInfo.addEventListener( Event.COMPLETE, onCompletelyDownloaded );
			loaderInfo.addEventListener( ProgressEvent.PROGRESS, onProgressMade );
			stage.stageFocusRect = false;
		}
 
		public function onCompletelyDownloaded( event:Event ):void
		{
			removeChild( loadingProgress );
			gotoAndStop(3);
			showMenuScreen();
		}
 
		public function onProgressMade( progressEvent:ProgressEvent ):void
		{
			loadingProgress.setValue( Math.floor( 100 * loaderInfo.bytesLoaded / loaderInfo.bytesTotal ) );
		}
 
		public function showMenuScreen():void
		{
			menuScreen = new MenuScreen();
			menuScreen.addEventListener( NavigationEvent.START, onRequestStart );
			menuScreen.x = 0;
			menuScreen.y = 0;
			addChild( menuScreen );
 
			stage.focus = menuScreen;
		}
 
		public function onAvatarDeath( avatarEvent:AvatarEvent ):void
		{
			var finalScore:Number = playScreen.getFinalScore();
			var finalClockTime:Number = playScreen.getFinalClockTime();
 
			gameOverScreen = new GameOverScreen();
			gameOverScreen.addEventListener( NavigationEvent.RESTART, onRequestRestart );
			gameOverScreen.x = 0;
			gameOverScreen.y = 0;
			gameOverScreen.setFinalScore( finalScore );
			gameOverScreen.setFinalClockTime( finalClockTime );
			addChild( gameOverScreen );
 
			removeChild( playScreen );
			playScreen = null;
 
			stage.focus = gameOverScreen;
		}
 
		public function onRequestStart( navigationEvent:NavigationEvent ):void
		{
			playScreen = new AvoiderGame();
			playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
			playScreen.x = 0;
			playScreen.y = 0;
			addChild( playScreen );
 
			removeChild( menuScreen );
			menuScreen = null;
 
			stage.focus = playScreen;
		}
 
		public function onRequestRestart( navigationEvent:NavigationEvent ):void
		{
			restartGame();
		}
 
		public function restartGame():void
		{
			playScreen = new AvoiderGame();
			playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath );
			playScreen.x = 0;
			playScreen.y = 0;
			addChild( playScreen );
 
			removeChild( gameOverScreen );
			gameOverScreen = null;
 
			stage.focus = playScreen;
		}
	}
}


There’s not a lot to change, really.

Now, let’s take a look at the play screen:

screenshot

Look at all those enemies below the visible area! None of them can be garbage collected until the game is finished, as they’re still on the screen and in the army array. If the player manages to stay alive for a long time, these enemies will just pile up and slow the game down to a crawl. So I think we’d better change this.

First, let’s get rid of that one enemy that’s always “manually” created in the constructor function. Just delete these three lines:

?View Code ACTIONSCRIPT3
47
48
49
var newEnemy = new Enemy( 100, -15 );
army.push( newEnemy );
addChild( newEnemy );

You don’t have to do this, but I don’t think we need it any more, since we’ve got plenty of enemies being generated randomly. Also, I think we should use the LevelData class if we want to create enemies at specific positions.

Next we have to remove the enemies from the army.

Removing Objects From An Array

Here’s something that trips everyone up the first time they try it. We want to remove any enemy that gets too far below the play screen, right? So, it seems all we need to do is this:

?View Code ACTIONSCRIPT3
158
159
160
161
162
163
164
165
166
167
168
169
170
for each ( var enemy:Enemy in army ) 
{
	enemy.moveABit();
	if ( PixelPerfectCollisionDetection.isColliding( avatar, enemy, this, true ) ) 
	{
		gameTimer.stop();
		avatarHasBeenHit = true;
	}
	if ( enemy.y > 350 )
	{
		//remove enemy here
	}
}

(That’s in the onTick() function, and lines 166-169 are the new ones. I’ve left out the actual removal code for now to make this point.)

This code will often lead to problems. See, Arrays are like lists, and each item on the list is a reference to an actual object. For example, army[0] refers to the first enemy in the army, army[1] to the second enemy, and so on.

When we write for each ( var enemy:Enemy in army ), it’s like saying “set enemy = army[0], then do the following code, then set enemy = army[1], then do the following code, and repeat until we reach the last member of army”.

Here’s the trick. If our for each gets to, say, the enemy stored at army[4], and decides to remove it from the array, then rather than making army[4] empty, it will instead refer to the enemy in army[5]. army[5] will, in turn, change to refer to army[6], which will change to refer to army[7], and so on. But our for each loop says, “right, we’ve dealt with army[4], now let’s move on to army[5]” — and it misses out the new ***army[4]* entirely**.

To illustrate this, let’s suppose we’ve got a numbered shopping list (we’re very organised shoppers). We look for each item in turn, rather than grouping them into aisles they’re likely to be on (so OK we’re not that organised), and if the shop doesn’t have any, we cross it off the list and move the numbers down. I’ll use bold to indicate the item we’re currently looking for. We start with item #1:

  1. Bacon
  2. Peas
  3. Apples
  4. Cabbage
  5. Potatoes

They’ve got bacon, great. Now onto item #2:

  1. Bacon
  2. Peas
  3. Apples
  4. Cabbage
  5. Potatoes

They have peas too. Yum. Now for item #3:

  1. Bacon
  2. Peas
  3. Apples
  4. Cabbage
  5. Potatoes

They don’t have apples, damn. Cross it off and re-number the list:

  1. Bacon
  2. Peas
  3. Cabbage
  4. Potatoes

Wait, nothing’s bold any more, what are we looking for? Well, we just did item #3, so I guess we should move on to item #4:

  1. Bacon
  2. Peas
  3. Cabbage
  4. Potatoes

Ah, excellent, they have potatoes. Well, that’s everything we wanted except apples, right? Not bad. Except then we get home and, damnit, we totally missed out cabbage! How can we make our famous “baconed potatoes with peas and cabbage” tonight? We can’t, the dinner party is ruined, and we’re the laughing stock of the town.

Er, back to programming.

This means that during this tick, we don’t check army[4] for collisions, we don’t check to see whether it’s got too low on the screen, or anything like that. This can cause really weird behaviour that appears to occur randomly — at least until you understand the reason.

The solution is simple. All we need to do is start at the end and work backwards. So in the shopping example, we start with #5:

  1. Bacon
  2. Peas
  3. Apples
  4. Cabbage
  5. Potatoes

Now onto #4:

  1. Bacon
  2. Peas
  3. Apples
  4. Cabbage
  5. Potatoes

(They do have cabbage.) Next is #3:

  1. Bacon
  2. Peas
  3. Apples
  4. Cabbage
  5. Potatoes

No apples, so cross it off and re-number the list:

  1. Bacon
  2. Peas
  3. Cabbage
  4. Potatoes

Where were we? Oh yes, #3. On to #2 then:

  1. Bacon
  2. Peas
  3. Cabbage
  4. Potatoes

Got peas, finally we do #1:

  1. Bacon
  2. Peas
  3. Cabbage
  4. Potatoes

And yes, they still have bacon. Success!

We can’t force a for each loop to work backwards, unfortunately, so we have to put a bit more work in. Remember back in Part 5 I introduced the while loop? To recap, it’s basically an if statement, but the code inside the curly braces gets run over and over again for as long as the statement is true. We can use a while loop to run through an Array backwards, like this:

?View Code ACTIONSCRIPT3
var i:int = army.length - 1;    //int just means a whole number
var enemy:Enemy;
while ( i > -1 )
{
	enemy = army[i];
	//do other code here
	i = i - 1;
}

See how that works?

  • var i:int — an int is just like a Number, but it only allows whole numbers, i.e. 1 and 5 are fine, but 2.38 isn’t
  • army.length – 1 — since the first item in an array is numbered 0, the second item will be numbered 1, the 20th will be numbered 19, and so on. army.length is the number of items in the Array, so army.length – 1 is the number of the last item in the Array
  • var enemy:Enemy; — we’re not setting it to refer to any specific enemy just yet
  • while ( i > -1 ) — as long as i is equal to 0 or higher, army[i] will refer to an enemy within the army Array
  • i = i – 1 — this is very important! This is what makes the code move backwards in the Array. Forget this, and you’ll get an infinite loop, and have to wait 30 seconds for Flash to decide something fishy is going on and stop running the game.

Let’s see how this looks in our onTick() function:

?View Code ACTIONSCRIPT3
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
var i:int = army.length - 1;
var enemy:Enemy;
while ( i > -1 )
{
	enemy = army[i];
	enemy.moveABit();
	if ( PixelPerfectCollisionDetection.isColliding( avatar, enemy, this, true ) ) 
	{
		gameTimer.stop();
		avatarHasBeenHit = true;
	}
	if ( enemy.y > 350 )
	{
		//remove enemy here
	}
	i = i - 1;
}

Now we need to remove the enemy’s reference from army. Every Array has a built-in splice() function for this. We pass this two numbers; the first is the position of the object we want to remove within the array (so in this case, that’s i), and the second is the number of objects we wish to remove (in this case, 1). Replace that “remove enemy here” comment as so:

?View Code ACTIONSCRIPT3
169
170
171
172
if ( enemy.y > 350 )
{
	army.splice( i, 1 );
}

Save and run your game, stretch it vertically again, and start playing:

screenshot

As the enemies reach a certain point, they are removed from the army, and so their moveABit() functions no longer get run and they stop moving. It’s pretty funny to watch, but sadly we can’t, as we need to removeChild() them if we want to clear them out of the player’s computer’s memory:

?View Code ACTIONSCRIPT3
169
170
171
172
173
if ( enemy.y > 350 )
{
	removeChild( enemy );
	army.splice( i, 1 );
}

Run your game with this code and you’ll see that the enemies just vanish after they get to a certain point below the screen. Great!

Tidying Up After Yourself

Aside from variables and addChild(), there’s another way of setting a reference to an object: event listeners! Sometimes functions can behave like objects; when you write “addEventListener( Event.WHATEVER, someFunction )”, the someFunction event handler function is treated like an object. As long as the event listener exists — i.e., as long as you don’t run “removeEventListener( Event.WHATEVER, someFunction )” — someFunction cannot be garbage collected.

Fortunately, Flash gives us a really easy way of dealing with this. It’s called weak referencing. You can tell Flash to use a “weak reference” to an event handler when creating an event listener, and when it does the garbage collection, it’ll ignore any such weak references. That means that if a function has a bunch of weak references, it’ll be garbage collected.

Obviously this takes a lot of work out of our hands, which is great. And all we have to do is change the way we add our event listeners, from this:

?View Code ACTIONSCRIPT3
addEventListener( Event.WHATEVER, someFunction );

to this:

?View Code ACTIONSCRIPT3
addEventListener( Event.WHATEVER, someFunction, false, 0, true );  //weak ref

Please ignore the third and fourth parameters for now; they basically control the order in which an event listener should be triggered, and I don’t want to go into details at the minute (check here for more info).

Setting the fifth parameter to true like this will force Flash to use these weak event listeners. Grant Skinner recommends getting into the habit of doing this for all listeners, and I agree.

Here’s what the document class looks like with all event listeners weakly referenced:

?View Code ACTIONSCRIPT3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package 
{
	//Avoider Game Tutorial, by Michael James Williams
	//http://gamedev.michaeljameswilliams.com
 
	import flash.display.MovieClip;
	import flash.events.Event;
	import flash.events.ProgressEvent;
	public class DocumentClass extends MovieClip 
	{
		public var menuScreen:MenuScreen;
		public var playScreen:AvoiderGame;
		public var gameOverScreen:GameOverScreen;
		public var loadingProgress:LoadingProgress;
 
		public function DocumentClass() 
		{
			loadingProgress = new LoadingProgress();
			loadingProgress.x = 200;
			loadingProgress.y = 150;
			addChild( loadingProgress );
			loaderInfo.addEventListener( Event.COMPLETE, onCompletelyDownloaded, false, 0, true );
			loaderInfo.addEventListener( ProgressEvent.PROGRESS, onProgressMade, false, 0, true );
			stage.stageFocusRect = false;
		}
 
		public function onCompletelyDownloaded( event:Event ):void
		{
			removeChild( loadingProgress );
			gotoAndStop(3);
			showMenuScreen();
		}
 
		public function onProgressMade( progressEvent:ProgressEvent ):void
		{
			loadingProgress.setValue( Math.floor( 100 * loaderInfo.bytesLoaded / loaderInfo.bytesTotal ) );
		}
 
		public function showMenuScreen():void
		{
			menuScreen = new MenuScreen();
			menuScreen.addEventListener( NavigationEvent.START, onRequestStart, false, 0, true );
			menuScreen.x = 0;
			menuScreen.y = 0;
			addChild( menuScreen );
 
			stage.focus = menuScreen;
		}
 
		public function onAvatarDeath( avatarEvent:AvatarEvent ):void
		{
			var finalScore:Number = playScreen.getFinalScore();
			var finalClockTime:Number = playScreen.getFinalClockTime();
 
			gameOverScreen = new GameOverScreen();
			gameOverScreen.addEventListener( NavigationEvent.RESTART, onRequestRestart, false, 0, true );
			gameOverScreen.x = 0;
			gameOverScreen.y = 0;
			gameOverScreen.setFinalScore( finalScore );
			gameOverScreen.setFinalClockTime( finalClockTime );
			addChild( gameOverScreen );
 
			removeChild( playScreen );
			playScreen = null;
 
			stage.focus = gameOverScreen;
		}
 
		public function onRequestStart( navigationEvent:NavigationEvent ):void
		{
			playScreen = new AvoiderGame();
			playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath, false, 0, true );
			playScreen.x = 0;
			playScreen.y = 0;
			addChild( playScreen );
 
			removeChild( menuScreen );
			menuScreen = null;
 
			stage.focus = playScreen;
		}
 
		public function onRequestRestart( navigationEvent:NavigationEvent ):void
		{
			restartGame();
		}
 
		public function restartGame():void
		{
			playScreen = new AvoiderGame();
			playScreen.addEventListener( AvatarEvent.DEAD, onAvatarDeath, false, 0, true );
			playScreen.x = 0;
			playScreen.y = 0;
			addChild( playScreen );
 
			removeChild( gameOverScreen );
			gameOverScreen = null;
 
			stage.focus = playScreen;
		}
	}
}


And here’s AvoiderGame:

?View Code ACTIONSCRIPT3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
package 
{
	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;
	import flash.media.SoundChannel;
 
	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;
		public var downKeyIsBeingPressed:Boolean;
		public var upKeyIsBeingPressed:Boolean;
		public var leftKeyIsBeingPressed:Boolean;
		public var rightKeyIsBeingPressed:Boolean;
		public var backgroundMusic:BackgroundMusic;
		public var bgmSoundChannel:SoundChannel;	//bgm for BackGround Music
		public var enemyAppearSound:EnemyAppearSound;
		public var sfxSoundChannel:SoundChannel;	//sfx for Sound FX
		public var currentLevelData:LevelData;
 
		public function AvoiderGame() 
		{
			currentLevelData = new LevelData( 1 );
			setBackgroundImage();
 
			backgroundMusic = new BackgroundMusic();
			bgmSoundChannel = backgroundMusic.play();
			bgmSoundChannel.addEventListener( Event.SOUND_COMPLETE, onBackgroundMusicFinished, false, 0, true );
			enemyAppearSound = new EnemyAppearSound();
 
			downKeyIsBeingPressed = false;
			upKeyIsBeingPressed = false;
			leftKeyIsBeingPressed = false;
			rightKeyIsBeingPressed = false;
 
			useMouseControl = false;
			Mouse.hide();
			army = new Array();
 
			avatar = new Avatar();
			addChild( avatar );
			if ( useMouseControl )
			{
				avatar.x = mouseX;
				avatar.y = mouseY;
			}
			else
			{
				avatar.x = 200;
				avatar.y = 250;
			}
 
			gameTimer = new Timer( 25 );
			gameTimer.addEventListener( TimerEvent.TIMER, onTick, false, 0, true );
			gameTimer.start();
 
			addEventListener( Event.ADDED_TO_STAGE, onAddToStage, false, 0, true );
		}
 
		public function onBackgroundMusicFinished( event:Event ):void
		{
			bgmSoundChannel = backgroundMusic.play();
			bgmSoundChannel.addEventListener( Event.SOUND_COMPLETE, onBackgroundMusicFinished, false, 0, true );
		}
 
		public function onAddToStage( event:Event ):void
		{
			addEventListener( KeyboardEvent.KEY_DOWN, onKeyPress, false, 0, true );
			addEventListener( KeyboardEvent.KEY_UP, onKeyRelease, false, 0, true );
		}
 
		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;
			}
		}
 
		public function onKeyRelease( keyboardEvent:KeyboardEvent ):void
		{
			if ( keyboardEvent.keyCode == Keyboard.DOWN )
			{
				downKeyIsBeingPressed = false;
			}
			else if ( keyboardEvent.keyCode == Keyboard.UP )
			{
				upKeyIsBeingPressed = false;
			}
			else if ( keyboardEvent.keyCode == Keyboard.LEFT )
			{
				leftKeyIsBeingPressed = false;
			}
			else if ( keyboardEvent.keyCode == Keyboard.RIGHT )
			{
				rightKeyIsBeingPressed = false;
			}
		}
 
		public function onTick( timerEvent:TimerEvent ):void 
		{
			gameClock.addToValue( 25 );
			if ( Math.random() < currentLevelData.enemySpawnRate )
			{
				var randomX:Number = Math.random() * 400;
				var newEnemy:Enemy = new Enemy( randomX, -15 );
				army.push( newEnemy );
				addChild( newEnemy );
				gameScore.addToValue( 10 );
				sfxSoundChannel = enemyAppearSound.play();
			}
			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 );
				}
			}
 
			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 );
			}
 
			var avatarHasBeenHit:Boolean = false;
			var i:int = army.length - 1;
			var enemy:Enemy;
			while ( i > -1 )
			{
				enemy = army[i];
				enemy.moveABit();
				if ( PixelPerfectCollisionDetection.isColliding( avatar, enemy, this, true ) ) 
				{
					gameTimer.stop();
					avatarHasBeenHit = true;
				}
				if ( enemy.y > 350 )
				{
					removeChild( enemy );
					army.splice( i, 1 );
				}
				i = i - 1;
			}
			if ( avatarHasBeenHit )
			{
				bgmSoundChannel.stop();
				dispatchEvent( new AvatarEvent( AvatarEvent.DEAD ) );
			}
 
			if ( gameScore.currentValue >= currentLevelData.pointsToReachNextLevel )
			{
				currentLevelData = new LevelData( currentLevelData.levelNum + 1 );
				setBackgroundImage();
			}
		}
 
		public function setBackgroundImage():void
		{
			if ( currentLevelData.backgroundImage == "blue" )
			{
				backgroundContainer.addChild( new BlueBackground() );
			}
			else if ( currentLevelData.backgroundImage == "red" )
			{
				backgroundContainer.addChild( new RedBackground() );
			}
		}
 
		public function getFinalScore():Number
		{
			return gameScore.currentValue;
		}
 
		public function getFinalClockTime():Number
		{
			return gameClock.currentValue;
		}
	}
}


The only other classes you need to alter are GameOverScreen and MenuScreen, and they each only have one event listener.

Do bear in mind that timers will still tick and sounds will still play until they get picked up by the garbage collector. Since we can’t predict when exactly that will happen, it’s best to stop them manually in your code.

That’s about it for garbage collection! OK, sure, there are smaller issues we could delve into, but these three basic ideas — set reference variables to null, remember to removeChild(), and use weakly-referenced event listeners — will cover you for all but the most huge and complex projects.

There’s just one more thing we should change before ending this tutorial…

Manually Declare Stage Instances

Remember wayyyy back in the first part of this tutorial, when I told you to tick the box marked “Automatically declare stage instances” in File > Publish Settings > Flash > Settings? Well, twelve parts later, we’re finally going to untick it.

What does it do, though? Actually it’s very simple. Take a look at the PlayScreen movie clip. It contains a score object and a clock object, but no avatar object. Now if you look at the corresponding AvoiderGame class, it has the code “public var avatar:Avatar;”, but there’s no corresponding code for the score or the clock.

All the “automatically declare stage instances” box does is make Flash insert the “public var clock:Clock;” and “public var score:Score;” lines when the game is run. If we uncheck it, we have to write those lines in ourselves.

Why would we want to give ourselves that extra work? One simple reason: Flash CS3′s built-in code editor is rubbish. When I write code, I use the free FlashDevelop software, which automatically inserts the various import statements, generates event handler code, lets me search through all my AS files for a specific keyword, and so much more. It’s not the only code editor of its kind, either. The one problem is… you have to turn off “automatically declare stage instances” in order to get the most from these kinds of software. I believe this small inconvenience is worth it.

Here’s how to do it. First, navigate to File > Publish Settings > Flash > Settings and untick that box:

screenshot

Now save your game, and try to run it. You’ll get a bunch of errors:

screenshot

All you need to do is double-click each error to open the corresponding class file, work out what object it’s talking about, and then add a “public var” statement to the top of that class.

For example, the first error I get is:

1120: Access of undefined property gameClock.

Double-clicking this takes me to AvoiderGame, line 122:

?View Code ACTIONSCRIPT3
122
gameClock.addToValue( 25 );

So, I just go to the top of the file, and manually declare the gameClock as a public variable of type Clock:

?View Code ACTIONSCRIPT3
12
13
14
public class AvoiderGame extends MovieClip 
{
	public var gameClock:Clock;

Repeat this process until you no longer get any errors. It won’t take long!

Then, go download FlashDevelop (or Eclipse). Enjoy ;)

Challenges

Like I said, this is the final part of this tutorial… but that doesn’t mean I won’t be writing more.

A lot of great ideas for new features have been suggested in the comments and via email, like having the avatar follow the cursor, making the play screen slowly turn to greyscale when the avatar dies, and putting a maze of walls around each level. I’d love to work these into the tutorial, but there are two problems: first, I can see it becoming 30 Parts long, which is going to be very overwhelming to anyone finding it for the first time; second, not everyone is going to want to implement every one of these features — but if they skip a specific part of the tutorial, it might be difficult to understand the next part that does contain a feature they’d like.

The best solution I can see is to declare this as a cut-off point. Future pieces of the tutorial will all start from this one part, so that anyone that’s got this far will be able to pick and choose from all the other pieces that will be written, without worrying about skipping over something important.

But this opens things up. Now anyone can write a piece, because everyone is starting from the same base. And that’s what this challenge is going to be.

I’d like you to write a tutorial. It doesn’t have to be long, it doesn’t have to be complex, it doesn’t even have to be in English. I know you guys have been experimenting with different features, often coming up with solutions I wouldn’t have thought of, and I’d like to see what you’ve invented.

FrozenHaddock and I have created a new site to catalogue all of these creations. It’s called AvoiderGame.com. If you’ve got a blog, you can post your tutorial there and we’ll link to it; if not, we’ll host it for you for free. Later on, we’ll put up the games you’ve created as well. For now, though, please contact me if you’d like to take part.

I’ll continue writing this tutorial for as long as I have ideas — and I hope you’ll join me.

Wrapping Up

Perhaps garbage collection wasn’t the most fun topic to end on, but I hope you can see how important it is. Using a while loop when removing objects from an array is also a useful tip to remember.

As always, you can grab a zip file with all the files relating to this part of the tutorial here.

Thank you very much for reading this tutorial, and for all the comments, emails, and kind words regarding it. I really appreciate it all :)

{ 275 comments… read them below or add one }

Jonathan August 13, 2011 at 5:10 pm

Hi Micheal,

I’ve really enjoyed the tutorials, they’ve been a great help. I’ve used a few of your techniques to put together my own little game, i’ll admit it probably as a few bugs and i’m no artist but let me know what you think http://www.mymessedupmind.co.uk/index.php/zombies

Kind regards

Jonathan

jdev August 22, 2011 at 11:16 pm

I’m designing an idle game based on this, but I’m having problems with the garbage collection. I want to have the player/avatar removeChild(candy) on contact, and add five points to the score, but I also want to have candy below a certain line removed. Each part works fine individually, but when I use both together I get: TypeError: Error #1009: Cannot access a property or method of a null object reference.
at PlayScreen/onTick()[C:\Users\jdev\Documents\flash\IdleCatcher\Classes\PlayScreen.as:53]
at flash.utils::Timer/_timerDispatch()
at flash.utils::Timer/tick()

I’m about 90% sure that the error means that the null object is a candy which has already been removed by the collision detection. But, um, it’s way beyond me.

package
{

import flash.display.MovieClip;
import flash.events.TimerEvent;
import flash.utils.Timer;
import flash.ui.Mouse;

public class PlayScreen extends MovieClip
{

public var candyStorm:Array;
public var newCandy:Candy; 
public var player:Player; 
public var gameTimer:Timer; 

public function PlayScreen() 
{
    player = new Player();
    Mouse.hide();           
    var newCandy = new Candy( 100, -15 );           
    candyStorm = new Array();
    candyStorm.push( newCandy );
    addChild( newCandy );

    gameTimer = new Timer( 25 );
    gameTimer.addEventListener( TimerEvent.TIMER, onTick, false, 0, true )
    gameTimer.start();
    addChild( player )
}

public function onTick( timerEvent:TimerEvent ):void
{           
    player.x = mouseX;
    player.y = mouseY;

    if ( Math.random() &lt; 0.1 )
    {
        var randomX:Number = Math.random() * 400;
        var newCandy:Candy = new Candy( randomX, -15 );
        candyStorm.push ( newCandy )
        addChild ( newCandy )
    }
    var i:int = candyStorm.length - 1;
    var candy:Candy;

    while ( i &gt; -1 )
    {
        candy = candyStorm[i];
        candy.moveABit();
        if ( candy.y &gt; 350 )
        {
            candy.parent.removeChild( candy );
            candyStorm.splice( i, 1 );
        }
        if( PixelPerfectCollisionDetection.isColliding( player, candy, this, true ) )
        {
            candy.parent.removeChild( candy );
            gameScore.addToValue( 5 );
        }               
        i = i - 1;
    }
}

}

}

Kcrik August 27, 2011 at 11:45 pm

Hey jdev,

The issue you get is because of that :

    if ( candy.y &gt; 350 )
    {
        candy.parent.removeChild( candy );
        candyStorm.splice( i, 1 );
    }
    if( PixelPerfectCollisionDetection.isColliding( player, candy, this, true ) )
    {
        candy.parent.removeChild( candy );
        gameScore.addToValue( 5 );
    }

I assume you aren’t a programmer originally :-) , that’s probably why you made this mistake :-) .

You’re first checking if the Candy is beyond a certain point, if it is, then remove it from the parent and from the array. But you’re checking if the same candy is colliding with the character, and if it is, you remove it from the parent.

So if the object is an object fill in both condition, and so it will enter in both if, the first if will get rid of the candy, and then you will try to get rid of this candy again, but it has been already removed…so it will try to remove something that already doesn’t exist any more.

You should do

    if ( candy.y &gt; 350 )
    {
        candy.parent.removeChild( candy );
        candyStorm.splice( i, 1 );
    }
    else if( PixelPerfectCollisionDetection.isColliding( player, candy, this, true ) )
    {
        candy.parent.removeChild( candy );
        gameScore.addToValue( 5 );
    }

Let me know whether if sorts out your issue. It should definitely do the trick :-)

jdev August 28, 2011 at 12:06 am

Kcrik: That wasn’t how I solved it, but I did eventually get it to work. Thanks for the help, though. Now I’m on to new reasons my code won’t work. But the game itself mostly works (except for a second “enemy” type, which I’m going to // out of the code for the time being). I think I’m going to upload it once I figure out how to save and load high scores, which I think I’ve almost got a handle on, and then maybe work on adding the second candy class if I get a good response.

Kcrik August 28, 2011 at 12:35 am

Hey out of curiosity, how did you solve your first problem ?

Sorry for the mistake in English by the way :-)

jdev August 28, 2011 at 1:47 am

I think I just added candystorm.splice to both sections of the code in while{}.

ExplosionsHurt September 4, 2011 at 9:35 am

Is the memory still freed up if I just run removeChild() and not splice() ?

Kcrik September 4, 2011 at 10:10 am

Hey jdev,

If you have candystorm.splice in both sections, when one of your object collide with your character and are out of the area where they should be on screen (that’s unlikely, but you never know), then it will take 2 objects off the screen when it shouldn’t :-)

I think you should still juse a if and else if, it makes more sense to it :-)

ExplosionsHurt-> if you don’t use splice on the array of objects…then the object will still be in this array, so no memory freed up. RemoveChild stop the object to be rendered, so it will save running time, but not a lot, you won’t even notice it.

To completely freed up the memory, you need to completely get rid of the object. Although, keeping your object in the array with a state like active or not, it will stop you having to recreate a new object each time you want to create one, and save running time :-)

jon September 25, 2011 at 9:32 pm

Hi Michael

loving your tutorial – thank you very much.

as a noobie with no programming knowledge whatsoever I am completely impressed that you have been able to put this into a format I can (mostly) understand

I am having one issue that I can `t seem to get around – but no-one else has posted a comment about, so I assume it is something obvious, I am just missing it.

I am trying to maunally declare stage instances, am getting the error ‘access of undefined property backgroundContainer. ‘ for each of the level backgrounds

I can `t figure out what to put in the public var backgroundContainer:???;

eg I put
public var gameScore:Score;
public var gameClock:Clock;

& that seemed to clear p those errors.

Any help would be greatly appreciated

jon September 25, 2011 at 10:40 pm

hi again

sorry for the multiple post, I think I figured out the backgroundContainer, I put

public var backgroundContainer:BackgroundContainer;

& it went to the next (last) series of errors, finalScore, bestScore, & finalClockTime in GameOverScreen.as & tried

            public var finalScore:FinalScore;
    public var bestScore:BestScore;
    public var finalClockTime:FinalClockTime;

but am getting the errors
Type was not found or was not a compile-time constant: FinalScore.
Type was not found or was not a compile-time constant: BestScore
Type was not found or was not a compile-time constant: FinalClockTime

now these are all dynamic text fields in the gameover screen – how do I define the property of these dynamic text fields?

Jason October 24, 2011 at 5:45 pm

<>
import flash.text.TextField;

public var finalScore:TextField;
public var bestScore:TextField;
public var finalClockTime:TextField;

should do it

Bigfoot November 14, 2011 at 5:43 am

Hey, I’m back to trying to create that game that continues to swivel around in my head… but I’ve been hitting some roadblocks the last couple days… I’m pretty sure I’m going down the road of male pattern baldness because of this.
I read your article on using Flash IDE to create levels, and that led me to downloading the newest version of MonsterDebugger …which I couldn’t get to work with Flash CS3 (the old version of MD worked sooo easily!) so I downloaded Flash Develop — you only told us about that, what… two years ago? — and now MonsterDebugger works! …buuut I can’t quite figure out how to write a class for, say, ‘enemy’… everytime I do, my enemy doesn’t show up.
Now, I read this link:
http://www.flashdevelop.org/community/viewtopic.php?f=13&t=8819
So I’m familiar with why it happens… but I can’t figure out how to do it right – could you point me in the right direction, pleeeease??
Thanks

Bigfoot November 24, 2011 at 1:43 am

I guess I just needed to keep researching in order to find the answer…
Here’s the link that explained it to me, in case anyone else is interested…
http://www.onegiantmedia.com/compiling-with-flash-develop-3-and-flexsdk-using-custom-classes-and-designs-in-the-flash-library
Thanks! :)

Kcrik December 1, 2011 at 8:43 pm

Hey,

I am using FlashBuilder on my side :-)

Developing the class in FlashBuilder and the art in Flash IDE is so much better :-)
Cause basically, you can’t have the errors most people get here, like “Type was not found or was not a compile-time constant: FinalScore.”

Cause you have to Embed the MovieClip, etc… in the code, so you have correctly embedded it or you haven’t :-)

James Ebert December 5, 2011 at 6:45 pm

for some reason the line “stage.stageFocusRect = false;” is giving me “Error #1009: Cannot access a property or method of a null object reference.at Game()” when I comment out this line the game runs no issues, but of course I have a bright white border, any reason why this would be a null object? is it a different command in CS5?

Dude January 5, 2012 at 4:25 pm

Hi

If i want my game to automatically redirect to a URL after the game is over, how would i do it? i cant find a solution.

of course the code needs to be added to the GameOverScreen class but where exactly?

one suggestion was inserting getURL(“http://www.yoursite.com/yourpage.html”); on the last frame but this is for as2 because as3 tries to stay away from timeline based coding.

ayumilove January 25, 2012 at 9:28 am

@James Ebert:

Make sure your stage is either:
1) not deleted
2) stage is assigned with an object.
3) the container has stage

If your stage is deleted, Flash obviously could not find the stage, let alone the property of that stage.

If the variable stage is not assigned with an object, Flash could not associate that property to that variable as well.

If you are referring to an object but that object does not have a stage property, then this error will also occur. For example, MovieClip has stage, but if you made your own custom class, it will not have stage, unless its extended from MovieClass.

Valentin Baltadjiev March 19, 2012 at 6:28 pm

Here is my result fomr this tutorial (I added some things):
http://www.kongregate.com/games/WishmasterBG/simple-avoider

I want to thank you very much, for this awesome tutorial, it really helped me to understand the bases of Flash and AS3

Brannie May 12, 2012 at 3:06 pm

Hey Michael,

Ive followed pretty much your entire tutorial (and editting + adding some stuff) But I have no idea how to add lives to the game. Do you think you could make another tutorial involving lives? :)
Or at least give me some hints as to how to get started on it?

oatlol June 2, 2012 at 12:55 pm

I think you should make a tutorial about adding achievements. Making them pop up and the having a list of all the ones you have got. I don’t know any tutorials that can offer this so I am asking you. Please make it if you are not busy :)

nolan July 4, 2012 at 6:20 pm

Thanks for the tutorial; really helpful in making the switch from as2 to as3. Keep up the great work.

tomas October 7, 2012 at 12:49 am

TypeError: Error #1009: No se puede acceder a una propiedad o a un método de una referencia a un objeto nulo.
at AvoiderGame/onTick()[C:\Users\xela\Downloads\ganso\curso flas cs\Classes\AvoiderGame.as:247]
at flash.utils::Timer/_timerDispatch()
at flash.utils::Timer/tick()

Madcowe October 17, 2012 at 12:29 am

I was having a lot of trouble with the

stage.stageFocusRect = false;

like a lot of the comments said but did not find any clear answer neither here nor on google.

I knew what the problem was, that when that statement was made, there was no focus, therefore the problem… but even after I downloaded the zip file, temporarily /* */’d my code and pasted your’s, the same error appeared.

So I started manually allocating the stage focus to each new stage that was added, omitting the stageFocusRect code and the whole thing still worked.

But when I placed the code again the same error appeared.

I had the idea of putting it right after the loading code had been executed (which by the way, doesn’t work on my end because I’m using flash cs5) and so I did and it now works!

For anyone having trouble and the error 1009, here’s where I put that part of the code to make it work:

public function onCompletelyDownloaded( event:Event ):void
        {
            gotoAndStop(3);
            showMenuScreen();

        stage.stageFocusRect = false;
        stage.focus = menuScreen;
    }</pre>

Also anyone please do tell me if by some chance this is not recommended.

Thanks

Pol Stax November 27, 2012 at 1:40 am

Ey man, i ve just did some changes of this excellent tutorial!!

This is my game : Space [Stuff] Around !!! Enjoy and Thanks you !!

http://www.microwebd.com/Juegos/spaceshit.html

Félix Moreno February 7, 2013 at 5:46 pm

This code:

if (lppw.hitTestObject(paddle1)) {
                        paddle1.scaleX = paddle1.scaleX * 1.5;
                        removeChild(lppw);
                        LPPWArmy.splice(lppw, 1);
                        if (paddle1.scaleX > 3) {
                            paddle1.scaleX = 3;
                        }
                    }
                    else if (lppw.hitTestObject(paddle2)) {
                        paddle2.scaleY = paddle2.scaleY * 1.5;
                        removeChild(lppw);
                        LPPWArmy.splice(lppw, 1);
                        if (paddle2.scaleY > 3) {
                            paddle1.scaleY = 3;
                        }
                    }
                    else if (lppw.hitTestObject(paddle3)) {
                        paddle3.scaleX = paddle3.scaleX * 1.5;
                        removeChild(lppw);
                        LPPWArmy.splice(lppw, 1);
                        if (paddle3.scaleX > 3) {
                            paddle3.scaleX = 3;
                        }
                    }
                    else if (lppw.hitTestObject(paddle4)) {
                        paddle4.scaleY = paddle4.scaleY * 1.5;
                        removeChild(lppw);
                        LPPWArmy.splice(lppw, 1);
                        if (paddle4.scaleY > 3) {
                            paddle4.scaleY = 3;
                        }

Gives me this error:
ArgumentError: Error #2025: El objeto DisplayObject proporcionado debe ser un elemento secundario del llamador.
at flash.display::DisplayObjectContainer/removeChild()
at Game/onTick()
at flash.utils::Timer/_timerDispatch()
at flash.utils::Timer/tick()

Leave a Comment

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

Anti-Spam Protection by WP-SpamFree

{ 1 trackback }

Previous post:

Next post: