localToGlobal, globalToLocal, locoToLoco

This week’s BMWS update is the result of some significant technical milestones both recent and past that enable the new Mage and Pikeman features. The new Mage magic shield and the Pikeman helmet features are brought to you by some nifty utility functions.

One of the significant challenges in flash games is being able to manage and use coordinate systems. Every MovieClip has its own relative coordinate space and may be a parent to any number of children MovieClips (each with their own coordinate spaces). To make maters more interesting, these MovieClips can be skewed, scaled and rotated independently meaning that their coordinate systems rarely lineup with the global coordinate system.

With respect to game objects represented by one or more MovieClips, it becomes tricky when you want to determine how to compare coordinate information from one game object to another. For example, the GameLevel MovieClip may contain a PikeUnit and within the Pikeman there are multiple body parts, one of which is a helmet. Let’s say that the Pikeman is in the middle of the GameLevel screen at position 1000 (out of a total width of 2000). The Pikeman’s helmet, however, exists in the Pikeman’s coordinate system and relative to the origin (where the feet are) the Helmet is at position -50,0.

The goal of this last BMWS update was to allow for armor parts to pop-off if the armor takes enough damage. The challenge is how do we take a helmet that is at position -50,0 and exists in the Pikeman MovieClip and place it in the GameLevel MovieClip as an independent object in a spot that is virtually the same spot but in a new coordinate system.

You can’t simply take the helmet and place it in GameLevel with “addChild” because the Helmet’s previous x/y coordinate values are preserved. So doing so will place the helmet at position -50,0 relative to the GameLevel’s origin which is at the top left portion of the screen and thus using GameLevel.addChild(Helmet) will place the helmet at the left edge and slightly below the top of the screen). We run into similar issues with respect to scale and rotation.

The key to solving this issue is understanding these MovieClip member functions:

1
2
localToGlobal(point:Point):Point
globalToLocal(point:Point):Point

The first function localToGlobal takes a point object that represents a point value relative to the local coordinate system of the “this” MovieClip and returns the same point as it relates to the global space (stage).

With our helmet example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// instance reference names:
// gameLevel is a MovieClip with stage unit coordinates
// pikeUnit is a MovieClip child of gameLevel
// helmet is a MovieClip child of pikeUnit

var helmLocalPoint:Point;
var helmGlobalPoint:Point;
var helmet:MovieClip; // shorthand reference

// helmet is a child of pikeunit which is a child of gameLevel
helmet = pikeunit.helmet;

// local point relative to the pikeUnit coordinate system
helmLocalPoint = new Point(helmet.x, helmet.y);

trace(helmLocalPoint); // outputs (x=0, y=-50)

// global point relative to stage coordinate system
// (same coordinate system as gameLevel in this example)
helmGlobalPoint = gameUnit.localToGloba(helmLocalPoint);

// the helmGlobalPoint is virtually the same spot visually
// but the values are relative to the global coordinate system
trace(helmGlobalPoint); // outputs (x=1000, y=524)

If you were to draw a circle at helmGlobalPoint its center point will perfectly line up with the visual location of the helmet.

So using these functions enable me to make helmets pop off the heads of pikemen.

When the unit’s helmet has no more hit points:
Step 1: make existing helmet invisible
Step 2: store the helmet’s local position, scale, and rotation information
Step 3: load a copy of the same helmet and update its scale, rotation, and position using the stored pikeman’s helmet information and converting those values to relative values in the GameLevel (stage) space.
Step 4: add the helmet copy to the GameLevel (make it visible).

The new helmet should now exist in its new coordinate space but appear to be placed in the same exact location relative to the pikeman’s head.

This example shows how you would convert from localToGlobal, but you may want to go the other direction in which case you use globalToLocal. An example of this seen when a projectile hits a mage’s magic shield.

A projectile exists just like a pikeman in the GameLevel space. When a projectile hits a magic shield it causes an energy shockwave to be displayed in a masked MovieClip. This visual effect briefly exposes the magic shield. In order to achieve this effect we must perform something similar to the helmet example but going from global coordinates to local coordinates. We now want to take a projectile’s global position and place a shockwave in a child clip of an MagicShield movieclip. Think of MagicShield as you would a Pikeman MovieClip in that it exists as a child of GameLevel. Placing a shock wave inside the MagicShield is like placing a helmet inside a Pikeman.

Understanding localToGlobal and globalToLocal is an essential fundamental Flash development skill.

The above examples show how to go convert points between local and global coordinate systems but what if you want to convert points from a local position in one DisplayObject to another local position in a different DisplayObject. This is often the case in LostVectors games especially. For example, the Space Combat Simulation Training 001 uses this concept for overlaying HUD indicators like the current target highlight box or the current target pointer arrow. The Space Combat game was especially tricky because game view uses a scrolling camera that can be rotated and zoomed (scale change) rather than a fixed view that uses the stage’s global coordinate system which is what Bowmaster does.

To help convert between two DisplayObject coordinate systems I created the following utility function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/***
 * @author Jason Reinsvold
 * Returns a point object that represents the x/y position of where pt is as a local point of
 * the toDomain. The fromDomain is where pt currently resides. The return newPt is the point
 * that exists relative to the toDomain but overlaps exactly with the pt with respect to the
 * global point coordinates.
 *
 * This function is useful for determining the equivalent point across different coordinate
 * systems. For example, if you have a UI window that is scaled and moved and you have a global
 * overlay highlighting system for drawing boxes over all objects. The overlay will draw boxes
 * relative to its own coordinate system but it needs to be able to determine what points are
 * equivalent to the UI window. This function helps with that conversion.
 */

public static function translatePoint(pt:Point, fromDomain:DisplayObject, toDomain:DisplayObject):Point
{
   var newPt:Point;
   newPt = pt.clone(); // clone point so that original point is not modified
   newPt = fromDomain.localToGlobal(newPt);
   newPt = toDomain.globalToLocal(newPt);
   return newPt;
}

This function first converts the input point relative to the “fromDomain” to a global point and then takes that new global point and converts it to a local point relative to the “toDomain”.

Also, this function is generalized such that it will act like localToGlobal or globalToLocal provided you pass the stage as either toDomain or fromDomain respectively (this is because one of the internal localToGlobal or globalToLocal calls returns the same point value, acting like a no op).

I also created similar functions to handle converting scaling and rotating but doing this was a bit more challenging because there are no built-in localToGlobalScale or localToGlobalRotation functions.

Bookmark the permalink.

5 Responses to localToGlobal, globalToLocal, locoToLoco