A lot of tutorials talk about designing levels in code, using XML or an array of arrays, or just setting x and y properties of various objects. This approach works, but it feels primitive. I mean, I spent Β£500 on a piece of professional design software, and you’re telling me I should lay out my levels with a text editor?
No thanks, Chuckles!
Fortunately, it’s really not that difficult to use the Flash IDE (that is, the actual Flash CS3/CS4 program) to design levels for games, and in fact using it in this way makes it easier for us to separate level design, art assets, and code. It even lets us create in-game level editors without much effort at all!
This article is an introduction to using Flash as a level editor, explaining how we can grab information about the symbols on the stage. In the next part we’ll take this further.
###The Most Basic (and Common) Method
There’s a very simple method people have been using since AS2 was popular.
In the above screenshot, I’ve got a number of *Platform* movie clips, each with an instance name of *platform1*, *platform2*, and so on. Same deal with the enemies. The player’s character has an instance name of *hero*.
All of these are either placed directly onto the stage, or is inside a movie clip that is linked to an AS file which contains all the code to power the engine. (The details aren’t important.)
We can then write collision detection code like this:
if ( ( hero.hitTestObject( platform1 ) ) || ( hero.hitTestObject( platform2 ) ) )
{
//deal with this collision
}
…where “dealing with the collision” involves checking which side of the platform the hero is hitting and stopping it falling if necessary.
Obviously that *if* statement is going to get very long! There are other problems, too; when we add a new platform we have to add a new clause to the *if* statement; removing platforms is similarly messy.
We need a way to loop through all the platforms, without having to manually add new platforms to the loop.
###Automatic Loops
In AS2 it was common to write code like this:
for (i = 1; i < 10; i++) {
var currPlatform = this["platform" + i];
//check currPlatform for collision with hero
//and deal with it
}
this["platform" + i]
gets a reference to the variable whose name is in the square brackets -- so this["platform" + 1]
is equal to platform1
. As long as all our platforms have instance names that follow this pattern, the above code will deal with looping through them without any further changes on our part.
Well... except we have to change that i < 10
if we have more than 10 items. And we have to make sure that no two platforms have the same name. And, OK, it might be a bit of a pain to name every single platform.
We can do better. In AS3, every MovieClip (indeed, every DisplayObjectContainer) has a function, getChildAt() that returns each display object that has been "addChild()-ed" to them. This includes all the library symbols that you drag on to the clip at design time. If we use this with the numChildren property we can easily loop through every single object in the level:
var childIndex:int = 0;
while ( childIndex < currentLevel.numChildren )
{
currentLevel.getChildAt( childIndex ).alpha = 0.5;
childIndex++;
}
(Here I'm assuming that we've made a new movie clip library symbol, dragged a bunch of objects into it, and have created a new instance of it called currentLevel
.)
That's just a simple demo that shows how to create such a loop, and sets the alpha of every item in the level to 50% to prove it works. Note: this entire article assumes that you have turned on *Automatically declare stage instances* in the Publish Settings. This may interfere with the rest of your code; we'll look at getting around this in a follow-up article.
Thanks to OOP, we don't need to give every platform an instance name. Instead, we can just give the platform symbol a *class* in the library -- let's call it *Platform*, for simplicity's sake. In our code, we can use the is
keyword to see whether the child of the level that we are currently looking at is an instance of the Platform class:
var childIndex:int = 0;
while ( childIndex < currentLevel.numChildren )
{
if ( currentLevel.getChildAt( childIndex ) is Platform )
{
if ( hero.hitTestObject( currentLevel.getChildAt( childIndex ) ) )
{
//deal with this collision
}
}
childIndex++;
}
That code could be run every tick.
Of course, we can simplify this:
var childIndex:int = 0;
var currentChild:DisplayObject;
while ( childIndex < currentLevel.numChildren )
{
currentChild = currentLevel.getChildAt( childIndex );
if ( currentChild is Platform )
{
if ( hero.hitTestObject( currentChild ) )
{
//deal with this collision
}
}
childIndex++;
}
...and support other classes of object:
var childIndex:int = 0;
var currentChild:DisplayObject;
while ( childIndex < currentLevel.numChildren )
{
currentChild = currentLevel.getChildAt( childIndex );
if ( currentChild is Platform )
{
if ( hero.hitTestObject( currentChild ) )
{
//deal with this collision
}
}
else if ( currentChild is Enemy )
{
if ( hero.hitTestObject( currentChild ) )
{
//deal with this collision
}
}
childIndex++;
}
In fact, we could just move most of the logic in here. For example, how about the enemy's movement?
else if ( currentChild is Enemy )
{
currentChild.moveABit();
if ( hero.hitTestObject( currentChild ) )
{
//deal with this collision
}
}
Looks good, but unfortunately currentChild.moveABit()
will cause an error. That's because all Flash knows about *currentChild* is that it's a DisplayObject -- and DisplayObjects don't have a function called moveABit()
. That's OK; we can use *casting*:
else if ( currentChild is Enemy )
{
(currentChild as Enemy).moveABit();
if ( hero.hitTestObject( currentChild ) )
{
//deal with this collision
}
}
(The as
keyword just tells Flash, "treat this object *as* an instance of this class".)
###Sub-Classes of Objects
Suppose you wanted to create a new type of platform:
We'd set the class of this new platform to *WoodPlatform*, and change the old platform's class to *GrassPlatform* to be consistent.
The obvious thing to do next is change our code like so:
currentChild = currentLevel.getChildAt( childIndex );
if ( ( currentChild is GrassPlatform ) || ( currentChild is WoodPlatform ) )
{
if ( hero.hitTestObject( currentChild ) )
{
//deal with this collision
}
}
...but this leaves us with a similar problem to before; whenever we want to add a new class of platform, we'll have to edit our code.
No need to worry, though, because we've got *inheritance*. Just create a new AS file like so:
package
{
import flash.display.MovieClip;
public class BasePlatform extends MovieClip
{
public function BasePlatform()
{
}
}
}
Now, whenever you create a new platform symbol, just set BasePlatform
as its *base class*, like so:
If you don't create the *WoodPlatform* class yourself, Flash will do it for you -- and it'll make it *extend BasePlatform*, too. This means that any instance of the WoodPlatform class counts as being an instance of the BasePlatform class, as far as the is
keyword is concerned, so if you do this for all your platform symbols, you can simplify the troublesome code:
currentChild = currentLevel.getChildAt( childIndex );
if ( currentChild is BasePlatform )
{
if ( hero.hitTestObject( currentChild ) )
{
//deal with this collision
}
}
Of course, you can do the same thing with enemies, and any other group of objects that share a common class.
Right now we're looping through all the on-stage objects once per tick, and dealing with them as we come to them. But what happens if we want to check, for example, whether *any* bullet is colliding with *any* enemy? The normal method would be to use a nested loop -- but the only way we can do that here is to loop through all the on-stage objects *again*.
We need to split everything into separate arrays. Let's look at how to do that.
###Arrays of Classes
I'd like to have a few arrays; one containing references to all the platforms, one for all the enemies, one for all the bullets, and so on. That way, I could organise my tick()
function like so:
public function tick():void
{
for each ( var platform:BasePlatform in _platformsArray )
{
if ( hero.hitTestObject( platform ) )
{
//deal with collision
}
}
for each ( var enemy:BaseEnemy in _enemiesArray )
{
enemy.moveABit();
if ( hero.hitTestObject( enemy ) )
{
//deal with collision
}
}
for each ( var bullet:BaseBullet in _bulletsArray )
{
bullet.moveABit();
for each ( var enemy:BaseEnemy in _enemiesArray )
{
if ( bullet.hitTestObject( enemy ) )
{
//deal with collision
}
}
}
}
(That code could be optimised, but you get the idea.)
Not only does this allow shorter loops, and remove the need to cast elements of an array every time, it also lets us remove an object from the stage without destroying our means of access to it.
Getting the objects into the arrays is really simple. At the start of the level, we just call a function createLevelFromMovieClip()
that works like this:
public function createLevelFromMovieClip( p_level:MovieClip ):void
{
_currentLevel = p_level;
_enemiesArray = new Array();
_platformsArray = new Array();
_bulletsArray = new Array();
var childIndex:int = 0;
var currentChild:DisplayObject;
while ( childIndex < _currentLevel.numChildren )
{
currentChild = _currentLevel.getChildAt( childIndex );
if ( currentChild is BasePlatform )
{
_platformsArray.push( currentChild );
}
if ( currentChild is BaseEnemy )
{
_enemiesArray.push( currentChild );
}
childIndex++;
}
}
You could also make one master array of all the items inside the movie clip, then filter() it down to separate the platforms from the enemies.
###Wrapping Up
We've separated the code from the layout to a great extent, and now we can use Flash's toolbox to add, move and resize level objects without having to worry about changing the engine to deal with them.
The next step is separating the layout from the art, coping with multiple levels and objects with more properties than just position (like HP, for instance), and looking at the possibilities for in-game level editing.
Click here to read on π
{ 38 comments… read them below or add one }
great!
in as3, everything is more easy. π
You are an absolute genius … :p
Thanks guys! Also thanks to Snurre for pointing out I forgot to put
childIndex++
in my while loops. Whoops…It’s good to see how much work are in your articles. cheers!
Good article. I hope this is a begining of a mini series, because I’m really looking forward to the next step!
Cheers π
And yep, I’ll be writing another post on this topic soon — probably this weekend.
So it seems I’m in the top Google results for [“No thanks, Chuckles!”]. That’s pretty funny. I feel I should mention that I came across the phrase in my favourite web comic about talking dinosaurs.
great explanation.
Cheers mitomane π
I’m planning to make a level editor for my box2D based platformer game. And after reading your article, I feel like, “why should I make my own level editor if the flash IDE itself can be used as a level editor?”
This really is a great Idea. Genius!
Thanks for sharing π
Thanks Ecky — that’s exactly how I feel! Glad you liked it π
Funnily enough I’ve been working with Box2D myself recently, so I’ll be writing about that soon too.
Great article!
I necessarily use it in my new game.
Thanks a lot.
Cheers, buagaga.
Where does currentLevel come from? I understand you can drag an item to the stage and export it for actionscript, but how do you make everything you drag onto the stage a child of that one object?
Oh, good point, I didn’t explain that very clearly. The idea is, you create a new movie clip in the library, edit it, drag in a bunch of objects (platforms, etc), then export it for actionscript and create an instance called
currentLevel
.Cheers for your comment, I’ve updated the tutorial to clear that up.
What should I do to think like you… I’m jealous you are a genius !
.-= QuakeboyΒ΄s last blog: Simflex Image Gallery – Full Free Flex Image Gallery =-.
Ahaha thanks man. Best comment ever π
I am having a problem placing the level in another frame. I want to do this because I am making a game menu (frame 1 = menu and frame 2 = level on main timeline). When I try to do so it comes up with “TypeError: Error #1009: Cannot access a property or method of a null object reference. at Level$iinit()”. It does work when I put the level in the first frame though. Why? Here is my code:
}
Hey Brandon,
I avoid using timeline code for anything other than preloaders as it just causes too many problems. To fix your error #1009, start by following my debugging guide to narrow down the line and situation that’s causing it.
Also, is Level your document class, or is it the class for one specific movie clip?
Level is my document class. Thanks for the help, I’ll remake it without the timeline.
Oh! In that case, try:
Hmm, actually, I suspect that won’t work. You’ll probably need to add a listener for
Event.RENDER
. (I think RENDER is the right one.) Try it out, see if it works. See, I think the problem’s because the objects don’t really exist until Flash loads that particular frame — so you need to force it to go to that frame.But yeah, single-frame documents are generally easier to deal with π
I figured out a solution! I attached the Level class to the 2nd frame instead of the stage and voila. I don’t understand it, but I’m going to make games like this from now on.
Sorted! π
Do you know what would be great? being capable to transform FlashIDE into a true level editor, how? well it basically has almost everything a level editor features:
-rule
-objects adding and removing
-info panel for every individually selected object
-importing objects
Exept one thing, exporiting for xml…
Do you know if there is someway to script this? like a component or something so. Imagine a script that could read the object type, x, y, width, height proepties of all visual objects into the IDE canvas and write taht onto an xml file. So that we could them load and position that objects read from an xml. So we could forget of the boilerplate code for checking if is a platfrom, or what type of enemy is, blabla…
That would be optimal at least from the SDK dev side.
Yes yes yes π I agree completely.
Ahh exporting for XML was actually going to be the next part of this tutorial, if I ever got round to writing it π It makes so much sense.
In vaguely-related news, have you seen Ogmo Editor?
Looks interesting above all ’cause is the first flash oriented level editor i see, i may try it at some point, although with level editing i think more in free objects than grids or tiles, it depends on the type of game you are working on of course. Anyway , now i am trying the flash IDE to xml level aproach, has gone very well so far.
Awesome. Got anything to show?
I wish, have some projects on hand, but in very early development stages. You are the one who should show us something, how long ago did you you write about blitting…
Oh man, I’ve been told!
OK, yes, I do have something, just not posted on my blog yet: Part 1, Part 2.
Thanks so much!!!!
I’ve been making so many pages of code for everything in classes. I had about twelve .as files and a game with a million glitches and this is so much simpler.
Oh.. thanks, James! I’m glad you found it useful π
This was an awesome tutorial! Thank you!
However… I feel like if you build a pretty large level in this way, the while loops will consume a lot of computer resources…
Perhaps I’m exagerating, but isn’t there any way to optimize this a bit more?
Hi I was just wondering… where do you get the:
from?
And how come you’re declaring the variables inside the function if the other function is going to use them? Just wondering because it doesn’t seem to work for me π
And last question: Where do I put the function, do I just drop them in the main function? or?
Thanks in advance
I was able to implement the first part of the code into the Avoider Game AS file I’ve been working with, but once I get up to 14 enemies my frame rate starts really lagging… if I get up to 20 enemies on screen at once I might hit single digit frame rates! Is my computer just too weak, or is my code doing something retarded?
See, I put this in, and commented out the old code that used to deal with enemy movement and collisions
It works… but I can literally run circles around my enemys (when using my mouse π )
Hey Bigfoot, long time no see! I suspect it’s to do with the PixelPerfectCollisionDetection, but not 100% sure. Try sticking in some trace() statements that trace the current time before and after certain bits of code, and seeing which parts take the longest to run.
Hello Mike
I had a question about this. I was attempting to put the information for my IDE inside my classes that i add to the stage (such as my Hero and Enemies). I was doing this to provide a self contained way to add enemies (since mines will appear at random of all types) and the code for each could be nestled inside of each of the classes.
So how would i add the functionality of the Flash IDE to the Sub Class and not the main class themselves. IF there is a possible way?
Aw, this was a really nice post. Spending some time and actual effort to
generate a very good article⦠but what can I say⦠I procrastinate a whole lot and never seem to get anything done.