Changing a MovieClip’s registration point painlessly

And while I’m still in the subject, here’s a final example of how one can use getter/setters do add some functionality that doesn’t normally exist in a MovieClip: altering the registration point of a MovieClip.

This is some commonly requested functionality. There are several libraries out there that do the trick, I suppose, but a long time ago, during my first AS3 project, none of them worked the way I wanted to, so I had to build my own.

It works like this: the Class PointMovieClip – again, by lack of a better name – has two new properties, registrationX and registrationY. These define where the display object’s axis is located (inside its own coordinate space), and obviously, default to 0, 0. If you change them, it’s as if you changed the registration point.

// Moves the registration point of an object 10 pixels down and to the right from its original position
myPMC.registrationX = 10;
myPMC.registrationY = 10;

// Rotates the object from the new position
myPMC.rotation = 45;

The interesting thing about this example is that it works by overriding existing properties such as x and y, and adding some additional functionality to rotation, scaleX and scaleY. The getter/setters, in this case, are intercepting the user’s changes to those values and making it act accordingly, so other than adding registrationX and registrationY, it’s maintaining the same MovieClip API, but adding new functionality to it.

package com.zehfernando.display {
	import flash.display.MovieClip;
	import flash.events.Event;
	import flash.geom.Point;	

	/**
	 * @author Zeh Fernando
	 */
	public class PointMovieClip extends MovieClip {

		// Instance properties ----------------------------------------------------------------

		protected var _registrationX:Number;		// Registration point X
		protected var _registrationY:Number;		// Registration point Y
		protected var _x:Number;					// User-defined X
		protected var _y:Number;					// User-defined Y

		protected var setToUpdate:Boolean;			// Whether this instance is already set to update on the next Event.RENDER

		// ================================================================================================================
		// CONSTRUCTOR ----------------------------------------------------------------------------------------------------

		public function PointMovieClip() {
			super();
			
			// Reads the current __values to keep them
			_x = super.x;
			_y = super.y;
			_registrationX = 0;
			_registrationY = 0;
			fixPosition();
		}

		// ================================================================================================================
		// INTERNAL INSTANCE functions ------------------------------------------------------------------------------------

		protected function fixPosition(): void {
			// Using localToGlobal/globalToLocal is less precise than doing it mathematically, but the end result is more accurate inside Flash because it's in sync with Flash's positioning and rotating limitations
			var op:Point = new Point(0, 0);
			var rp:Point = new Point(_registrationX, _registrationY);
			rp = parent.globalToLocal(localToGlobal(rp));
			op = parent.globalToLocal(localToGlobal(op));
			super.x = _x - (rp.x - op.x);
			super.y = _y - (rp.y - op.y);
		}

		protected function requestPositionFix(): void {
			if (Boolean(stage) && !setToUpdate) {
				setToUpdate = true;
				stage.addEventListener(Event.RENDER, onRender, false, 0, true);
				stage.invalidate();
			}
		}

		// ================================================================================================================
		// EVENT functions ------------------------------------------------------------------------------------------------

		protected function onRender(e:Event): void {
			stage.removeEventListener(Event.RENDER, onRender);
			setToUpdate = false;
			fixPosition();
		}

		// ================================================================================================================
		// ACCESSOR functions ---------------------------------------------------------------------------------------------

		override public function get x(): Number {
			return _x;
		}
		override public function set x(__value:Number): void {
			_x = __value;
			requestPositionFix();
		}

		override public function get y(): Number {
			return _y;
		}
		override public function set y(__value:Number): void {
			_y = __value;
			requestPositionFix();
		}

		override public function set rotation(__value:Number): void {
			super.rotation = __value;
			requestPositionFix();
		}
		override public function set scaleX(__value:Number): void {
			super.scaleX = __value;
			requestPositionFix();
		}
		override public function set scaleY(__value:Number): void {
			super.scaleY = __value;
			requestPositionFix();
		}

		public function get registrationX(): Number {
			return _registrationX;
		}
		public function set registrationX(__value:Number): void {
			_registrationX = __value;
			requestPositionFix();
		}

		public function get registrationY(): Number {
			return _registrationY;
		}
		public function set registrationY(__value:Number): void {
			_registrationY = __value;
			requestPositionFix();
		}

	}
}

One last note: with this class, changing the registration point’s X and Y will move the object around, as it keeps the previous x and y positions instead of offsetting them to match; this is something I actually needed in my implementation as there were moments I tweened that value (together with x, y and rotation – basically, sliding, rotating rectangles on screen) and wanted the object to respond accordingly in a smooth fashion.

While there may be moments one needs to rotate a display object from a given point with the x and y changing immediately to match, I believe the above implementation to work better when animating things across screen.

  • Olegs Balss

    It would be much more usable if you would choose composition over inheritance.

  • Zeh

    Olegs: feel free to post your implementation to serve as an example.

  • Oleg you could just take this class and pass in the movieclip reference to the constructor, an init method, or add a property that sets it for composition usage.

  • Hassan

    I was thinking it would be better if could extend the class itself to add these methods to. I mean, use something like var mc:MovieClip = new MovieClip() and mc had the functionality that var mc:PointMovieClip = new PointMovieClip() has. Is that possible? I can’t speak english very well, sorry for my english.

  • Zeh

    Hassan: it’s a bigger problem, but the right thing to do would probably be using a decorator pattern. You’d still need a separate class, and to use it instead of your MC, but at least in this case you could apply it to *any* class. It’d work like that:

    var mc:MovieClip = new MovieClip();
    var pmc:PointMovieClip = new PointMovieClip(mc);

    Using decorators actually makes a lot more sense when you need to add features like registration points, coloring and such to existing classes – specially MC classes since it’s a lot more common for people to have their own display classes and they can choose *which* features to add.

    Remember this example is supposed to be an example of how you can extend properties and override original properties, it’s a getter/setter syntax example, instead of the best practice ever to add registration points to movieclips.

    PS. Your english is fine, don’t worry.

  • Hassan

    Thanks Zeh! I thought that this way will be easier for end-developer, but it seems that I was wrong. Using this method:

    var mc:MovieClip = new MovieClip();
    var pmc:PointMovieClip = new PointMovieClip(mc);

    is good, but can I use a code like this? var dmc:DesignerMovieClip = new DesignerMovieClip(pmc);? To have both functionality of DesignerMovieClip and pmc:PointMovieClip? Is it a good way to do this or not?

    Thanks! I really appreciate your notes!

  • Zeh

    Hassan: if both classes were implemented using a decorator pattern (or even a pseudo-decorator with composition), yeah, it’d work.

    The way they are now, in the examples, for you to combine the functionality, you’d have to merge the two classes, adding all the functions and methods from one class to the other, or extending from one class to the other.

  • nasim

    hi
    How can i use this class
    I have movie clip inside aother movieclip it’s empty andset (refrence point manually at middle of that) i draw wave by code inside it , i want to move and scalit ,I need to change it’s refrence point and registration point bye code plese help me .
    ( reg point means: the point that rotate scal… done depend on it)
    (efrence point means the (0,0) point , I want to draw in positive axies and set scale point at midddle of my wave , i get confused heeeeeeeeeelp

  • Noah

    But how do you implement this? Can you please give us a short example? This looks very promising but I can’t seem to get it done.

  • There is a bug on set rotation and scale after startDrag/stopDrag. Cause the _x and _y are old value.