Understanding Flash’s Coordinate Systems

by Michael James Williams on August 7, 2009 · 31 comments

in Articles,AS3 Concepts Explained,Maths

screenshot

Have you ever written code like this:

?View Code ACTIONSCRIPT3
newBullet.x = hero.gun.x;
newBullet.y = hero.gun.y;

…and found that the bullet appears in a seemingly random place compared to the hero’s gun?

Or have you ever traced tank.turret.rotation and found that it always returns zero, even when it’s clearly pointing diagonally?

Maybe you’ve compared the width of two identical objects, one obviously double the size of the other, and found that Flash thinks they are the same.

Every Flash dev I know has run across one of these and sat scratching their head — myself included!

So what’s going on?

Well, it’s all to do with addChild()

The Flash Bucket

Think of Flash as a bucket. When you look at a SWF, you’re looking into a container — a DisplayObjectContainer, to be precise.

To demonstrate, let’s suppose I’ve got three MovieClips (or Sprites, or Bitmaps, or whatever, it doesn’t matter), and I addChild() them to the document class:

?View Code ACTIONSCRIPT3
public class DocumentClass extends MovieClip
{
	public var bigM:BigM = new BigM();
	public var bigJ:BigJ = new BigJ();
	public var bigW:BigW = new BigW();
	public var shapes:Shapes = new Shapes();
	public function DocumentClass()
	{
		bigM.x = 25;
		bigJ.x = 160;
		bigW.x = 270;
		bigM.y = 140;
		bigJ.y = 140;
		bigW.y = 140;
		addChild( bigM );
		addChild( bigJ );
		addChild( bigW );
		addChild( shapes );
	}
}

screenshot

(I think you can guess which symbol has which instance name.)

When I say Flash is like a bucket, I mean it looks like this:

screenshot

When you addChild() a symbol, you drop it into the bucket, and it sits slightly above everything else that’s in there. Of course, unless the objects overlap, you don’t notice this.

Buckets of Buckets

Now let’s take a look at what happens with a more complicated display object. Suppose we make a new class like this:

?View Code ACTIONSCRIPT3
public class Shapes extends MovieClip
{
	public var greenTriangle:GreenTriangle = new GreenTriangle();
	public var blueCircle:BlueCircle = new BlueCircle();
	public var redSquare:RedSquare = new RedSquare();
	public function Shapes()
	{
		greenTriangle.x = 25;
		blueCircle.x = 160;
		redSquare.x = 270;
		greenTriangle.y = 270;
		blueCircle.y = 270;
		redSquare.y = 270;
		addChild( greenTriangle );
		addChild( blueCircle );
		addChild( redSquare );
	}
}

As a bucket, we can imagine that this symbol will look like this:

screenshot

…but we won’t see any of this inside the SWF unless we addChild() it to the document class:

?View Code ACTIONSCRIPT3
public var shapes:Shapes = new Shapes();
//...missed out some code here...
shapes.x = 0;
shapes.y = 0;
//...missed out some more code...
addChild( shapes );

Now the SWF will look like this:

screenshot

…but what about in bucket view? Well, we don’t just drop each new shape into the document class bucket; rather, we drop each shape into the Shapes bucket, and then drop the whole Shapes bucket into the document class bucket:

screenshot

OK, cool. Now, you can see that the M is just above the green triangle, so they should have the same x-values but different y-values, right? We can check with a trace():

?View Code ACTIONSCRIPT3
addChild( shapes );
trace( bigM.x, shapes.greenTriangle.x, bigM.y, shapes.greenTriangle.y );

This returns 25 25 140 270 as we’d expect. So far, no problems.

What if we move the M to the right by 50 pixels?

?View Code ACTIONSCRIPT3
addChild( shapes );
bigM.x += 50;
trace( bigM.x, shapes.greenTriangle.x, bigM.y, shapes.greenTriangle.y );

screenshot

The trace() now returns 75 25 140 270 — again, just as it looks. For the sake of completeness, let’s try moving the green triangle too:

?View Code ACTIONSCRIPT3
bigM.y += 10;
shapes.greenTriangle.y += 10;
trace( bigM.x, shapes.greenTriangle.x, bigM.y, shapes.greenTriangle.y );

screenshot

Now the trace() returns 75 75 140 270. Great. Let’s just set everything back how it was, now:

?View Code ACTIONSCRIPT3
addChild( bigM );
addChild( bigJ );
addChild( bigW );
addChild( shapes );
//no changes to any x-values

There’s one thing we haven’t tried, of course; moving the shapes object.

Stretchy Buckets

What happens if we move the entire shapes bucket 50 pixels to the right?

?View Code ACTIONSCRIPT3
addChild( shapes );
shapes.x += 50;

It looks like this:

screenshot

This time, the green triangle is 50 pixels to the right of the M, so presumably its x-value will be 50 bigger:

?View Code ACTIONSCRIPT3
addChild( shapes );
shapes.x += 50;
trace( bigM.x, shapes.greenTriangle.x );

Hang on… the trace() returns 25 25. It says the two x-values are the same. What’s happened?

It’s easier to understand in bucket view:

screenshot

It’s a little unclear, but the shapes bucket has shifted to the right, just as we told it to. This has caused the document class bucket to stretch wide enough to fit it — though note that the Flash player window still has the same view of the document class that it did before.

If you look at the shapes bucket, you can see that the green triangle is still as far away from the left-hand side of that bucket as it was before. And here’s where the confusion lies.

See, x and y are just values that say how far away an object is from the top-left corner of its parent — that is, the bucket to which the object is addChild()-ed.

Here, I’ll prove it. When you call addChild() on an object, its x- and y-values don’t change. Also, an object can only have one parent at any given time. This means we can change the green triangles parent from the shape class to the document class:

?View Code ACTIONSCRIPT3
addChild( shapes );
shapes.x += 50;
addChild( shapes.greenTriangle );

The green triangle is still a variable belonging to the Shapes class, which is why we have to write shapes.greenTriangle to access it, but now we’ve changed which bucket it’s in. The SWF looks like this:

screenshot

…and in bucket view, it looks like this:

screenshot

The triangle’s x-value is the same as before (we could trace it and prove it) but it now refers to its distance from the left edge of the document class, rather than from shapes. Also note that it’s dropped on top of everything else inside the document class bucket.

The same applies to rotation, width and height, but they’re a little harder to visualise.

Other Affected Properties

For this part, I’m going to go back to how things were at the start, with shapes in its normal place and the green triangle inside the shapes bucket.

Also, to make things simpler, I’ll draw a border around the edge of the Shapes class so that we can see it:

?View Code ACTIONSCRIPT3
public function Shapes()
{
	graphics.lineStyle( 6, 0x330066 );
	graphics.drawRect( 0, 0, 400, 380 );

(I’ve used those values because they’re the dimensions of my Flash movie.)

By default, shapes.rotation is zero, so let’s change it and see what happens:

?View Code ACTIONSCRIPT3
//etc
shapes.rotation -= 10;

screenshot

Not a surprising result. You probably won’t be surprised either to learn that the rotation values of each shape is still zero. Only their parent’s rotation is different.

What about the x- and y-values of the triangle now?

?View Code ACTIONSCRIPT3
trace( shapes.bigTriangle.x, shapes.bigTriangle.y );

This returns: 25 270, the same as before. It’s a little odd, because the distance from the triangle to the left side of the shapes bucket actually has changed this time, if we measure horizontally:

screenshot

What’s happened is, when we rotated shapes, we also rotated the x-axis and y-axis used by every child of shapes!

screenshot

We say that each DisplayObjectContainer has its own coordinate system (its own pair of x- and y-axes).

So instead of thinking as the triangle’s x-value as its distance from the left edge of its parent, we should think of it as the distance from the edge of its parent along the negative x-direction. The same goes for its y-value. (Remember, in Flash, y points “downwards”. Well, until you rotate it!)

This is kind of complicated when written out in words, so I like to sketch out a picture (like the one above) whenever I get confused.

How about width? Let’s set the rotation back to normal and try changing the width instead:

?View Code ACTIONSCRIPT3
addChild( shapes );
shapes.width *= 2;

screenshot

Here, the circle is clearly twice as wide as it is high.

?View Code ACTIONSCRIPT3
trace( shapes.blueCircle.width, shapes.blueCircle.height );

…and yet this trace returns 83 83. Once again, the shapes coordinate system has been changed; this time it’s been stretched.

screenshot

If we were to now set shapes.bigTriangle.x += 1, we would actually see the triangle move two pixels to the right. So the x-value of the triangle can no longer be measured in pixels, since pixels are a property of the computer screen.

What do we call them, then? Well, they’re just “units”. Units-that-would-be-equal-to-pixels-if-everything-were-at-its-correct-size is a more accurate name, but a little unwieldy.

What To Do About All This

The simplest way to avoid getting in a big mess is to make sure every single object we’d ever want to refer to in code has the same parent: the document class. That way, if two objects have the same x- and y-values, we know they are in the same place, because they must be using the same coordinate system.

Of course, this is ridiculously inconvenient. Fortunately, we can get the benefits of a shared coordinate system without the drawbacks.

There is a function, localToGlobal(), that will take the x- and y-values of a point in one coordinate system, and return the x- and y-values of the same point in the document class’s coordinate system.

What? OK, here’s an example. Suppose I move the shapes over to the right:

?View Code ACTIONSCRIPT3
addChild( shapes );
shapes.x += 135;
//no longer altering the width or rotation

screenshot

Now, we know that the green triangle’s x-value is going to say the same as before — the same as the M — if we trace it. And yet, we can see that in the coordinate system of the document class, it should have the same x-value as the J. We can use localToGlobal to show this:

?View Code ACTIONSCRIPT3
var triangleCoordinatesInsideShapes:Point = new Point( shapes.greenTriangle.x, shapes.greenTriangle.y );
var triangleCoordinatesInsideDocumentClass:Point = shapes.localToGlobal( triangleCoordinatesInsideShapes );
trace( bigJ.x, triangleCoordinatesInsideDocumentClass.x );

As you can see, localToGlobal is run on the shapes object, not the triangle; this is because we are converting from the shapes coordinate system.

That trace statement returns 160 160, just as we’d hope, showing that the green triangle and big J have the same x-coordinate relative to the document class.

What about the other way around? Use the globalToLocal() function. This takes a point in the coordinate system of the document class and returns the same point in any given object’s coordinate system, like this:

?View Code ACTIONSCRIPT3
var bigJCoordinatesInsideDocumentClass:Point = new Point( bigJ.x, bigJ.y );
var bigJCoordinatesInsideShapes:Point = shapes.globalToLocal( bigJCoordinatesInsideDocumentClass);
trace( bigJCoordinatesInsideShapes.x, shapes.greenTriangle.x );

That returns 25 25 — again, the coordinates match, but this time they’re given relative to shapes.

If you want to compare two objects that are in different containers, but neither of them is a child of the document class, you can combine the localToGlobal() and globalToLocal() functions.

For example, here I’ll add another instance of the shapes class, but I’ll set its alpha to 0.5 so that it looks transparent:

?View Code ACTIONSCRIPT3
public var transparentShapes:Shapes = new Shapes();
//...missed out some code here...
transparentShapes.x = 0;
transparentShapes.y = 0;
//...missed out some more code...
addChild( transparentShapes );
transparentShapes.alpha = 0.5;
transparentShapes.x += 135;

screenshot

(Note that I’ve also moved transparentShapes to the right by 135 units.)

Here, naturally, we’d find that shapes.bigTriangle.x and transparentShapes.bigTriangle.x were equal, because we haven’t moved either triangle within its parent. But we want to show that the transparent triangle has the same coordinates as the opaque circle. We need to chain a globalToLocal and a localToGlobal command:

?View Code ACTIONSCRIPT3
var circleCoordinatesInsideShapes:Point = new Point( shapes.blueCircle.x, shapes.blueCircle.y );
var circleCoordinatesInsideDocumentClass:Point = shapes.localToGlobal( circleCoordinatesInsideShapes );
var circleCoordinatesInsideTransparentShapes:Point = transparentShapes.globalToLocal( circleCoordinatesInsideDocumentClass );
trace( circleCoordinatesInsideTransparentShapes.x, transparentShapes.greenTriangle.x );

This trace gives us 25 25. Success!

What About Rotation and Width?

Rotation and width are slightly more awkward to deal with.

Rotation is just a matter of addition and subtraction. If we wanted to see what angle the opaque triangle was at in the coordinate space of the document class, we’d have to add shapes.rotation and shapes.greenTriangle.rotation. If we wanted to work out its angle in the coordinate space of the transparent shapes, we’d then have to subtract transparentShapes.rotation.

For widths and heights we have the getBounds() method. It’s used like this:

?View Code ACTIONSCRIPT3
var triangleBounds:Rectangle = shapes.greenTriangle.getBounds( this );
trace( triangleBounds.width );

That’ll give us the width of the triangle in the coordinate space of the document class (because we’re writing code inside the document class, that’s what this will refer to).

Note that the format is different to localToGlobal; you call the function as a method of the object whose width you want to find out, and pass the desired coordinate space as an argument — in localToGlobal its the other way around.

If for some reason you wanted to find the width of an object apart from any lines used to draw it, you could use the getRect() method instead. It’s called in the same way.

So that’s pretty much everything you’d need to know about coordinate systems in Flash. Any questions?

{ 30 comments… read them below or add one }

Ryan August 8, 2009 at 3:12 pm

I think I know where the inspiration for this post came from ;)

Nice job! I can’t say I completely understand it with the addChild and Shapes things, but this as3 doesn’t seem so bad. Perhaps I will have to switch soon. We’ll see.

.-= Ryan´s last blog ..Freelance Flash Games Forums Now Live! =-.

8bitjeff August 8, 2009 at 6:26 pm

Nice write-up, Michael! Pure in depth genius. -Jeff

.-= 8bitjeff´s last blog ..8/6/2009 Mochi First Impressions Game Review Mash-Up: Thursday, Aug 06, 2009 =-.

Michael Williams August 9, 2009 at 12:40 pm

@Ryan: Haha yep, it was your question the other day that made me think of this. So thanks ;)

Good to see your timeline for learning AS3 has made another jump: “eventually -> sometime soon -> soon” :P

@Jeff: Thanks!

Chetan Sachdev August 9, 2009 at 7:09 pm

@Michael
I enjoyed your post and thanks for sharing this. I liked the idea of looking at flash player as a bucket, it makes sense and clears my confusion on localToGlobal and globalToLocal :)

Thank you

.-= Chetan Sachdev´s last blog ..Debugging TLF source code =-.

Ryan August 10, 2009 at 1:26 am

Haha yeah. But it may have jumped back to sometime soon with news I heard from Cavalcade games in the fgl forums: http://www.flashgamelicense.com/view_thread.php?thread_id=10157&last_read_post_id=63128

I didn’t read all 3, but it sounded like it was about how as3 was taking a step towards being more powerful, but going away from being simple. Then, they just suggested features that flash should have. I think flash is taking suggestions for features, so that’s good at least.

One of them mentioned Haxe too, which I may have to check out. Their site claims they offer better performance and language features, but I’d have to see if it was worth learning over as3.

.-= Ryan´s last blog ..Freelance Flash Games Forums Now Live! =-.

Michael Williams August 10, 2009 at 4:50 pm

@Chetan: Thank you! I’m glad to have helped :)

@Ryan: Cheers for that link, interesting reading. This debate fires up every now and again, and it usually hinges around the “AS2 is easier” argument. The thing is, “easy” is a relative term — if you’re used to AS2 then AS3′s event listeners and forced class-based structure seems really hard, but if you’re used to AS3 then AS2 seems messy and often frustrating.

If they seemed like completely different languages for different purposes and different types of users, like AS2 and C# (or, heck, let’s go crazy: AS2 and Haskell), I don’t think there’d be so much of a problem. Trouble is, they’re both used to make Flash apps, and that “3″ makes it seem like AS3 is an incremental improvement to AS2.

Plus, there’s this notion of “making the jump” to AS3, like it’s a one-way path and once you’ve tried it you can never go back. Nonsense!

I recommend trying it out, just to have a go. See it as a separate language, as though you were going to have a go at learning Java or C# just for fun. I happen to know of a beginner-level tutorial for doing just that ;) You might find you hate it, in which case no harm’s been done and you can just stick with AS2, or you might enjoy it enough to want to learn more about it (while still doing other work in AS2).

Either way, for the sake of a couple days playing around, you don’t have to guess at an opinion any more.

Ryan August 10, 2009 at 11:16 pm

Good call. I’ll give it a try, within… a week we’ll say. Hopefully I don’t keep putting it off, but we’ll see.

.-= Ryan´s last blog ..Concepts First =-.

Bigfoot August 11, 2009 at 6:58 pm

Nice article, and the illustrations were incredibly helpful! (65% of the population are visual learners, and 86% of statistics are made up on the spot) It’s definitely one I’ll need to bookmark in my list of “Articles that I won’t memorize…so I need to remember them somehow”
Thanks for all your help. Your blog is an asset to the community.

Bigfoot August 11, 2009 at 7:05 pm

CommentLuv? What’s that?
Almost posted that… then I googled and found the answer to my own question. Suprisingly their website doesn’t really explain it well. Either that or you have to search for it (which I tried unsuccessfully)
had to change my website URL to the direct link in order for it to work

.-= Bigfoot´s last blog ..MJW Avoider Game ep11ch1 =-.

arxanas August 12, 2009 at 2:21 am

Odd, I already knew all this, I think it’s because of my experience in AS2. It didn’t fool me for more than 5 seconds when I first encountered it.

But very useful post to those who don’t understand!

Michael Williams August 12, 2009 at 6:36 pm

@Ryan: haha OK.

@Bigfoot: Thanks! Yeah I find doodling out a little diagram like that really helps me. Odd that CommentLuv didn’t work first time.

@Arxanas: Cheers :)
You mean it didn’t fool you the first time you encountered it in AS2, or in AS3?

arxanas August 13, 2009 at 4:15 am

Is there a way to rearrange the order of these containers? For example, in my game, I’m firing bullets, but they appear on top of the gun and start moving, as opposed to under, so it seems as though it’s actually leaving the gun. I know AS2 had depth control, but does AS3 have any such thing?

Although the logical solution is probably to create a container for the bullets (and organize every type of object in the game) beneath the gun…

Michael Williams August 13, 2009 at 5:09 pm

Sure — actually there are several ways. As I understand it, AS2 gave every object a “depth index”, right? AS3 does a similar thing, but with one big exception: there are no gaps. So, you can’t have an object with index 4 and then insert the next one at index 1000. Also, if you have objects at index 0, 1, and 2 (say), then insert another item at index 1, the objects currently at index 1 and 2 will move to indices 3 and 4.

The relevant functions are:

container.getChildIndex( child:DisplayObject ):int — gets the depth index of the child object within container.

container.setChildIndex( child:DisplayObject, index:int ) — sets the depth index of the child object within container.

container.addChildAt( child:DisplayObject, index:int ) — like addChild() except inserts the new object at the specified index within container.

container.getChildAt( index:int ):DisplayObject — gets the object within container at the given index.

container.swapChildren( child1:DisplayObject, child2:DisplayObject ) — swaps the indices of the two objects specified (must be within container)

container.swapChildrenAt( index1:int, index2:int ) — swaps the indices of the two objects that are at the given indices.

And of course you can combine these, like writing container.addChildAt( newBullet, getChildIndex( gun ) - 1 ) to add the new bullet underneath the gun.

Personally I really like using the method you suggested, separating objects into different containers. This is particularly useful for making a HUD that doesn’t scroll and a background that scrolls at a different rate to the main sprites.

arxanas August 14, 2009 at 2:15 pm

Ah, very useful. I remember someone at FrozenHaddock’s forum asked how to keep stuff on top (namely, a mute button). This would have been rather useful to whomever that was. Can indexes (indices, whatever) also go negative? Or is it all above zero?

And do these containers exist on certain indexes, which then have some sort of virtual “sub-indexes”?

Michael Williams August 15, 2009 at 12:41 am

The lowest object in a container has an index of 0, and none can be negative.

All the DisplayObjects within a DisplayObjectContainer have an index from 0 to ( number of display objects – 1 ). If one of those DisplayObjects happens to be a DisplayObjectContainer itself, and contain even more DisplayObjects, then each of those DisplayObjects has an index from 0 to ( number of display objects in the inner DisplayObjectContainer – 1 ). And so on.

It’s called a “composite pattern”; it’s self-similar, like a fractal. Hard to explain in text, haha!

arxanas August 15, 2009 at 2:29 am

Okay then, that’s what I needed to know. Thanks!

Porter August 20, 2009 at 2:07 am

That’s definitely the most in depth explanation I’ve ever seen. If ever I need to explain this to someone, this is definitely where I’ll send them, great work.

.-= Porter´s last blog: Do Sponsors Care About More Than CTR? =-.

Michael Williams August 20, 2009 at 12:16 pm

Thanks, Porter!

Free Games October 7, 2009 at 7:03 pm

Awesome post, thanks for all the detail. I’ve been struggling with learning actionscript and its websites like yours that keep allowing me to make slow and steady progress. Of course, I usually find websites like yours after I have beaten my head bloody against the wall trying to figure out some of these quirks! Thanks again!

.-= Free Games´s last blog: Square Jump =-.

Michael Williams October 9, 2009 at 4:48 pm

Thank you Free Games :)
Haha, yes the same thing happens with me, I often find posts just after I need them :P

Michael Williams October 21, 2009 at 5:29 pm

If you’re looking for a more practical application of all this, check out my new tutorial on parallax scrolling.

Alex June 22, 2010 at 11:39 am

Great tutorial
i am trying to develop my own flash game

Michael Williams June 22, 2010 at 3:52 pm

@Alex: Cool! How’s it going?

Don June 23, 2010 at 3:43 pm

Thank you for this,

Alas it is still too far beyond me, I was hoping to learn how you stop Y co-ords going down :-( I am trying to design a simple graph but everything I try makes the graph draw upside down. Maby in 20 years I will have learned enough to be able to “rotate” it as you suggest.

Thanks
Don

Michael Williams July 5, 2010 at 7:10 pm

Hey Don,

Having the y-axis point down is how most computer programs do it, I’m afraid. It means that the top-left corner is (0,0), which is often useful.

Have you tried multiplying all your y co-ords by -1, then adding the height of your stage?

Bratz October 3, 2010 at 4:57 pm

i see dude it should work i’ll try and get back to you

Bigfoot December 4, 2011 at 12:26 am

How would this fit into the avoidergame code – for example, if I was trying to get the coordinates of the mouse cursor (mouseX) in relation to avatar’s x-position (avatar.x) – everytime I try, the avatar traces that it’s at the same position of my mouse cursor, but it’s really about 50 units to the left!

Adrian January 5, 2012 at 7:46 pm

Pretty valuable piece of info.
Thank you.

moneyisshame July 25, 2013 at 3:28 pm

for the code

newBullet.x = hero.gun.x;
newBullet.y = hero.gun.y;

i would use this

newBullet.x = hero.x + hero.gun.x;
//get outside x, and then get inside x, add them together, become the one you are looking for, same as y
// + hero.gun.width if you want the bullet fire at the end
newBullet.y = hero.y + hero.gun.y;
// + hero.gun.height if you want the bullet fire at the bottom

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: