Browse the notebook

Category: DisplayObject

Rotation Property Weirdness

var boxA:Shape = Shape(addChild(new Shape()));
with (boxA.graphics) beginFill(0), drawRect(-10,-10,20,20);

var boxB:Shape = Shape(addChild(new Shape()));
with (boxB.graphics) beginFill(0), drawRect(-10,-10,20,20);

boxA.x = 100; 
boxA.y = 100;

boxB.x = 200; 
boxB.y = 100;

var rot:Number = 32750;

addEventListener(Event.ENTER_FRAME, onLoop);
function onLoop(evt:Event):void {
  rot += 1
  // will stop rotating 
  boxA.rotation = rot
  // will keep rotating
  boxB.rotation = rot % 360;
}

I recently became aware of a strange aspect of the rotation property on DisplayObjects. For some reason, once it’s value goes a little beyond ~32750 the DisplayObject will simply stop rotating. If you read the rotation property it is still changing, but there is no visual update – a quick check on the DisplayObject.transform.matrix property will show that the value has stopped.

The easy fix is to use mod before applying the value to the rotation property. Surprised I’ve never come across this one before. Maybe someone can shed some light on this.


// for people searching google for solutions to this problem I’ll add the following key words:
MovieClip stops rotating, DisplayObject stops rotating, rotation property broken, not rotating

Read snippet: Rotation Property Weirdness

globalToLocal() in 3D

[SWF(frameRate=60, backgroundColor=0x000000, width=500, height=500)]
stage.quality = "medium";
var frame:Sprite = Sprite(addChild(new Sprite()));
with (frame.graphics) beginFill(0xCCCCCC), drawRect(-200, -200, 400, 400), endFill();
frame.x = stage.stageWidth / 2;
frame.y = stage.stageHeight / 2;
frame.z = 100;

var canvas:Shape = Shape(frame.addChild(new Shape()));
var msk:Shape = Shape(frame.addChild(new Shape()));
with (msk.graphics) beginFill(0x00FF00), drawRect(-200, -200, 400, 400), endFill();
canvas.mask = msk

var txt:TextField = TextField(addChild(new TextField()));
txt.defaultTextFormat = new TextFormat("_sans", 12);
txt.x = txt.y = 10;
txt.textColor = 0xFFFFFF, txt.autoSize="left", txt.text = "Draw on the 3D plane...";

stage.addEventListener(MouseEvent.MOUSE_DOWN, onDown);
stage.addEventListener(MouseEvent.MOUSE_UP, onUp);
function onDown(evt:MouseEvent):void{
	canvas.graphics.lineStyle(4, 0x000000);
	var pnt:Point = frame.globalToLocal(new Point(mouseX, mouseY));
	canvas.graphics.moveTo(pnt.x, pnt.y);
	addEventListener(Event.ENTER_FRAME, onDraw);
}
function onUp(evt:MouseEvent):void{
	removeEventListener(Event.ENTER_FRAME, onDraw);
}

var t:Number = 0;
addEventListener(Event.ENTER_FRAME, onLoop);
function onLoop(evt:Event):void {
    frame.rotationY = 35 * Math.sin(t);
	frame.rotationX = 35 * Math.cos(t);
	t+=0.02;
}

function onDraw(evt:Event):void {
        var pnt:Point = frame.globalToLocal(new Point(mouseX, mouseY));
	canvas.graphics.lineTo(pnt.x, pnt.y);
}

This demo shows that globalToLocal() works with 3D – saving us the trouble of doing some extra math if we want to draw on 3D display objects…

Have a look at the swf….


Was made aware of this trick by watching a video that kevinSuttle sent me via twitter. The video is an interview with Chris Nuuja (one of the flash player engineers).

Read snippet: globalToLocal() in 3D

Isometric Transformation Matrix

var grid:Sprite = Sprite(addChild(new Sprite()));
var matrix:Matrix = new Matrix();
// make the grid sprite look isometric using the transform.matrix property
matrix.rotate(Math.PI / 4);
matrix.scale(1, 0.5);
matrix.translate(stage.stageWidth / 2, stage.stageHeight / 2);
grid.transform.matrix = matrix;

// draw a grid of circles to show that it does in fact look isometric
var rowCol:Number = 8;
var num:Number = rowCol * rowCol;

var diameter:Number = 40;
var radius:Number = diameter / 2;
var space:Number = diameter + 10;
var halfGridSize:Number = rowCol * space / 2;

grid.graphics.beginFill(0xFF0000);
for (var i:int = 0; i<num; i++){
   grid.graphics.drawCircle(i % 8 * space - halfGridSize, int(i / 8) * space - halfGridSize, radius);
}

This snippet shows how to use transform.matrix to make a DisplayObject look isometric. This can also be achieved with nesting.

In the case of this grid of red circles, we rotate it 45 degrees, scale it 50% on the y and move it to the center of the stage (lines 4-6).

Read snippet: Isometric Transformation Matrix

Bounds 3 Ways

// make a complex poly and position it randomly
var poly:Sprite = Sprite(addChild(new Sprite()));
poly.graphics.lineStyle(3, 0xFF0000);
poly.graphics.beginFill(0x00FF00);
for (var i:int = 0; i<10; i++) {
	poly.graphics.lineTo(Math.random()*100 - 50, Math.random()*100 - 50);
}
poly.x=Math.random()*stage.stageWidth;
poly.y=Math.random()*stage.stageHeight;

// get bound information:

// is in pixels (whole numbers)
trace("pixelBounds: ", poly.transform.pixelBounds);
// doesn't include stroke width
trace("getBounds: ", poly.getBounds(this));
// includes stroke width
trace("getRect: ", poly.getRect(this));

Three different ways to get the boundaries of a DisplayObject.

Read snippet: Bounds 3 Ways

Circles & cacheAsBitmap

[SWF(width=600,height=400,backgroundColor=0x000000,frameRate=30)]
var mcs:Vector.<MovieClip> = new Vector.<MovieClip>();
var num:int = 2000;
for (var i:int = 0; i<num; i++){
	var s:MovieClip = new MovieClip();
	s.graphics.lineStyle(0,0xFFFFFF);
	s.graphics.drawCircle(0,0, Math.random()*30 + 3);
	s.x = Math.random()*stage.stageWidth;
	s.y = Math.random()*stage.stageHeight;
       // comment out to kill your cpu
	s.cacheAsBitmap = true;
	addChild(s);
	s.vx  = Math.random() * 2 - 1;
	s.vy  = Math.random() * 2 - 1;
    mcs.push(s);
}
addEventListener(Event.ENTER_FRAME, onCenter);
function onCenter(evt:Event):void{
	for (var i:int = 0; i<num; i++){
		mcs[i].x += mcs[i].vx;
		mcs[i].y += mcs[i].vy;
	}
}

This was created in response to a question about the real speed gain of cacheAsBitmap. Just comment out the cacheAsBitmap line to see the difference.

Despite it’s extreme simplicity, this is actually a rather fun file to play with, change some of the graphics class method calls around and see what happens.

Read snippet: Circles & cacheAsBitmap

Static Stage and More…

package {
	import flash.display.*;

	public class Main extends MovieClip{
	
	    private static var _display:MovieClip;
	    private static var _stage:Stage;
	    
	    // use getters instead of public static vars so they 
	    // cannot be reset outside of Main
	    public static function get display():MovieClip{
	    	return _display;
	    }
	    
	    public static function get stage():Stage{
	    	return _stage;
	    }
	    
		public function Main(){
		   Main._display = this;
		   Main._stage = stage;
		   
		   // test out the static references
		   var t:Test = new Test();
		}
	}
}

class Test{
	public function Test(){
		// test out the static references
		with(Main.display.graphics){
			lineStyle(1, 0xFF0000);
			for (var i:int = 0; i<100; i++){
				lineTo(Math.random() * Main.stage.stageWidth, 
				       Math.random() * Main.stage.stageHeight);
			}
		}
	}
}

This snippet creates two private static variables that reference the stage and the main timeline/document class. It then uses getters to regulate the use of these static vars so that they cannot be reset from outside the Main class.

Sometimes the amount of extra coding you need to do to maintain a valid stage reference can be cumbersome… similarly, passing references of the document class all around your app can be annoying. If you don’t mind using two global vars in your app… this trick can come in handy.

What’s nice about using getters here is that if someone tries to do this:

Main.display = new MovieClip();

they’ll get an error… in flex, you even see that little red ex pop up next to this line of code if you write it 🙂 … that wouldn’t happen with a public static var….

Read snippet: Static Stage and More…

Matrix() 3 Point Skew

[SWF(width=500, height=500, backgroundColor=0xFFFFFF, frameRate=30)]

var box:Sprite = createSprite("Rect", 0, 0, 100, 100, 0xFF0000);

var d0:Sprite = drag(createSprite("Ellipse",  -5, -5, 10, 10));
d0.x = d0.y = 200;

var d1:Sprite = drag(createSprite("Ellipse",  -5, -5, 10, 10));
d1.x = 200
d1.y = 300;

var d2:Sprite = drag(createSprite("Ellipse",  -5, -5, 10, 10));
d2.y = d0.y;
d2.x = 300;

addEventListener(Event.ENTER_FRAME, onLoop);

function onLoop(evt:Event):void {
 
	var m:Matrix = new Matrix();
	m.scale((d2.x - d0.x) / 100,(d1.y - d0.y) / 100);
	m.translate(d0.x, d0.y);
	
	m.c =  (d1.x - d0.x) / 100
	m.b =  (d2.y - d0.y ) / 100
	
	box.transform.matrix = m;
}

function createSprite(shape:String, xp:Number, yp:Number, w:Number, h:Number, col:uint=0x444444):Sprite {
	var s:Sprite = new Sprite();
	s.graphics.beginFill(col);
	s.graphics["draw" + shape](xp, yp, w, h);
	addChild(s);
	return s;
}

function drag(target:*):*{
	target.addEventListener(MouseEvent.MOUSE_DOWN, function(evt:MouseEvent){ evt.currentTarget.startDrag(); });
	return target;
}

stage.addEventListener(MouseEvent.MOUSE_UP, function(){ stopDrag() });

The above creates a red rectangle that can be skewed by dragging 3 points. Why is this so cool you ask?

This is why (move your mouse)

The above was written in flash 7, before the Matrix object existed. So in order to create it I used this technique from Eric Lin.

Read snippet: Matrix() 3 Point Skew

Bring Display Object to Top

mc.addEventListener(MouseEvent.ROLL_OVER, onRollOver);
function onRollOver(evt:MouseEvent):void {
  addChild(MovieClip(evt.currentTarget));
}

This one is very simple, but it’s important to note that using addChild() on something that is already on the display list simply brings it to the top. Back in AS2 we used to do:

mc.swapDepths(1000);
Read snippet: Bring Display Object to Top

Isometric Box

stage.frameRate = 30;

for (var i:int = 0; i<100; i++){
    makeBoxSegment(200, 200 - i, i * 2);
}

function makeBoxSegment(xp:Number, yp:Number, col:uint):Sprite {
	var isoBox:Sprite = Sprite(addChild(new Sprite()));
	with (isoBox) scaleY = .5, y = yp, x = xp;
	var box:Shape = Shape(isoBox.addChild(new Shape()));
	box.rotation = 45;
	with (box.graphics) beginFill(col), drawRect(-50,-50,100,100);
	isoBox.addEventListener(Event.ENTER_FRAME, onRotate);
	return isoBox;
}

function onRotate(evt:Event):void {
	evt.currentTarget.getChildAt(0).rotation = mouseX;
}

An isometric box that rotates with the mouseX.

Read snippet: Isometric Box

Reset Registration Point


// display object target, value - "center" or "upperLeft"
function setRegistration(dsp:DisplayObjectContainer, v:String):void {
	var i:int;
	var child:DisplayObject;
	var b:Rectangle=getBounds(dsp);
	for (i = 0; i < dsp.numChildren; i++) {
		child=dsp.getChildAt(i);
		child.x+=b.x*-1;
		child.y+=b.y*-1;
	}
	if (v=="center") {
		dsp.x+=dsp.width/2;
		dsp.y+=dsp.height/2;
		for (i = 0; i < dsp.numChildren; i++) {
			child=dsp.getChildAt(i);
			child.x-=dsp.width/2;
			child.y-=dsp.height/2;
		}
	} else if (v == "upperLeft") {
		if (dsp.parent) {
			b=getBounds(dsp.parent);
			dsp.x=b.left;
			dsp.y=b.top;
		}
	}
}

// example:
// (make a MovieClip and fill it with, text, shapes etc...)

setRegistration(clip, "center");
// registration(clip, "upperLeft");

addEventListener(Event.ENTER_FRAME, onLoop);
function onLoop(evt:Event):void {
	clip.rotation+=1;
}

Teaching beginner flash… I’ve noticed people always get confused about the registration point. They think they should be able to move it with the IDE or ActionScript. I always say “You can’t change the registration point, you can only change the position of graphical elements in relationship to it.” Anyway, as a sort of joke one day after class I wrote the above function… it changes the registration of any DisplayObjectContainer to either “center” or “upperLeft”.

To test this code out, fill a MovieClip with a bunch of different things (text, graphics, scribbles)… give it a strange registration point and then run the above code on it. You’ll be able to tell that the registration has changed by the way the clip rotates. Although I haven’t been able to break this function, I think it’s possible that it doesn’t work under all circumstances.

Using reparenting is a more elegant solution to this issue. Maybe I’ll post that in the next couple days.

Read snippet: Reset Registration Point