Home Contact

[June 15, 2008]

Upon Reflection

Filed under: ActionScript — @ 8:54 pm

In ActionScript 3, classes (and functions as well) are objects that can be manipulated like any other object. The language provides us with some constructs to facilitate this, and they are an important part of the programmer’s toolkit.

We need to be able to traverse the connections among the Class, an instance of the Class, and the name of the Class.

From name to Class; from instance to Class

First, you want to be able to get hold of a Class object, either from its package path and class name:

import flash.utils.*;

var myClass:Class = getDefinitionByName("com.myDomain.myPackage.SomeClass");

or from an instance of the class:

var myClass:Class = myInstance.constructor;

Here are a few simple ways you can use these:

  • The Class object is a factory for making instances of its class, so using getDefinitionByName() you can write a factory method to instantiate objects of types specified by Strings, e.g. in an XML file:

    var myClass:Class = getDefinitionByName("com.myDomain.myPackage." + xmlData.@type);
    var myObject:Object = new myClass();
    
  • Run-time enforcement of an abstract class:

    public function MyClass()
    {
    	super();
    	if (this.constructor == MyClass)
    	{
    		throw new IllegalOperationError("AbstractException");
    	}
    }
    
  • Suppose a game character is collecting items such as gems or weapons or vegetables, and all you need to know about the items is their type. You don’t have to hang on to the items themselves; you can store the collection as a Dictionary indexed by class objects:

    private var _collection:Dictionary = new Dictionary(true);
    .
    .
    .
    private function collect(item:Object):void
    {
    	var itemClass:Class = item.constructor;
    	if (_collection[itemClass] == null)
    	{
    		_collection[itemClass] = 1;
    	}
    	else
    	{
    		_collection[itemClass]++;
    	}
    }
    

From Class to class name; from instance to class name

I’ll just show my getClassName() function here:

import flash.utils.*;

public function getClassName(o:Object):String
{
	if (o is Class)
	{
		var s:String = o.toString();	// "[class SoAndSo]”
		return s.substring(7, s.length - 1);
	}
	else
	{
		var fullClassName:String = getQualifiedClassName(o);
		var i:int = fullClassName.lastIndexOf(”::”);
		if (i == -1)
		{
			return fullClassName;
		}
		return fullClassName.slice(i + 2);
	}
}

The toString() method is defined in the root Object class, and it can be redefined in your own classes. It’s called whenever the String representation of an object is needed. For example in getClassName() I could have written

var s:String = "" + o;

which would have implicitly invoked o.toString(). The trace() function also uses toString(). So it’s a good idea to implement toString() in your classes. I usually start out by doing this:

public override function toString():String
{
	return getClassName(this);
}

and then add whatever specific data I need to show into it.

Related ActionScript 3 Tip of the Day by Senocular: #73

From Class to Class

Here’s the last basic class-slinging tool, and I believe it’s one that not many Flash programmers are aware of. Suppose you want to know if class A is derived from class B. You might think you’d have to make an instance of A and use the is operator on it. And that’s OK if you don’t have to come up with parameters for the constructor. But there’s a cleaner way:

public function isDerived(childClass:Class, ancestorClass:Class):Boolean
{
	if (ancestorClass.prototype.isPrototypeOf(childClass.prototype))
	{
		return true;
	}
	return false;
}

Now you may say yeccch, we’ve left the prototype behind, that’s old-school AS1 programming, and you’d certainly be correct in a way: In AS1 there was no way to simulate classes except by mucking with the prototype, and in AS2 the compiler simulated classes under the hood by mucking with the prototype, and in AS3 we have true class inheritance. So there’s almost certainly no need to simulate classes in AS3. But the prototype, and the isPrototypeOf() function, are not going away any time soon; they’re in the ECMA spec, so they must be retained in the language. I believe I can guarantee, though, that aside from this isDerived() function, there is NO other legitimate use case for isPrototypeOf() in AS3! The prototype itself is a different matter, and I mentioned a use case for placing properties there in a comment to my recent post Advanced Users May Choose.

Related ActionScript 3 Tip of the Day by Senocular: #74

What can you do with isDerived()? Suppose my game character is collecting various kinds of Gems, such as Rubies, Sapphires, and Diamonds, and various kinds of Vegetables, such as Rutabagas, Kohlrabi, and JerusalemArtichokes. He’s saving them in a Dictionary as in the example above, and I need to know how many Gems or how many Vegetables he has:

private function numInCollection(collectionClass:Class):uint
{
	var n:uint = 0;
	for (var key:* in _collection)
	{
		var c:Class = key as Class;
		if (c == collectionClass
		||  isDerived(c, collectionClass))
		{
			n += _collection[key];
		}
	}
	return n;
}

Summary of indexed collections in AS3

  • Object: properties indexed by Strings
  • Array: properties indexed by Numbers
  • Dictionary: properties indexed by Objects

Occasionally I meet a programmer who indexes an Array object’s properties using Strings. Of course this works, but it works because the array is an Object; if you’re not using Numbers as indices, then you’re just wasting space by declaring the collection as an Array. You could make it a MovieClip and it would work just as well!

[June 5, 2008]

Behind the Beyond, or Beyond the Behind

Filed under: ActionScript — @ 1:49 am

With the recent advances in Flash 3D and panoramas, and the fact that Flash 10, now out in beta, has intrinsic support for arbitrary warping of triangles, we can look forward to a future of increased performance and the complete disappearance of those wavy lines in panos. All of that fancy 3D stuff is based on matrix transformations, but lately I’ve been working with simpler analytic-geometry concepts, some of which turn out to be quite useful. Let’s look at a few.

We’ll start with the sameSide() function, which I’ll use to implement some more interesting functions:

// sameSide.as
package
{
    import flash.geom.Point;

    // are p1 and p2 on the same side of the line through a and b?
    public function sameSide(p1:Point, p2:Point, a:Point, b:Point):Boolean
    {
        var cp1:Number = cross(b.subtract(a), p1.subtract(a));
        var cp2:Number = cross(b.subtract(a), p2.subtract(a));
        return (dot(cp1, cp2) >= 0);
    }
}       

    import flash.geom.Point;

    function cross(a:Point, b:Point):Number
    {
        // z coordinate of the cross product; x and y coordinates are zero
        return a.x * b.y - a.y * b.x;
    }

    function dot(a:Number, b:Number):Number
    {
        // z vectors
        return a * b;
    }

A little AS3 note about the foregoing: The above code is the entire sameSide.as file; it’s a bit unusual in that it does not contain a class. AS3 requires that a source file expose a single external definition, which in this case is a function rather than a class. A function defined in this way is called a package-level function. The functions cross() and dot() that appear after the closing curly brace are visible only to code within the same file, and thus any access control specifier like “internal” or “private” would be redundant.

Of course you could make sameSide() a public static method of a Class, and cross() and dot() private static methods.

Now we implement some other functions based on sameSide():

// behind.as
package
{
    import flash.geom.Point;

       /**
         * Is p2 behind me when I stand at p0 and face p1?
         *
         */
        public function behind(p0:Point, p1:Point, p2:Point):Boolean
        {
            // slope of normal to the line p0p1:
            var m:Number = (p0.x - p1.x)/(p1.y - p0.y);
            // y-intercept of normal through p0:
            var b:Number = p0.y - m * p0.x;

            return !sameSide(p1, p2, p0, new Point(0, b));
        }
}
// beyond.as
package
{
    import flash.geom.Point;

       /**
         * Is p2 beyond p1 when I stand at p0 and face p1?
         *
         */
       public function beyond(p0:Point, p1:Point, p2:Point):Boolean
        {
            // slope of normal to the line p0p1:
            var m:Number = (p0.x - p1.x)/(p1.y - p0.y);
            // y-intercept of normal through p1:
            var b:Number = p1.y - m * p1.x;

            return !sameSide(p0, p2, p1, new Point(0, b));
        }
}

Cute, eh? Nice to know if something is behind you, or beyond me.

Here’s another:

package
{
    import flash.geom.Point;

/**
* Is the point p inside the triangle abc?
*/
    public function pointInTriangle(p:Point, a:Point, b:Point, c:Point):Boolean
    {
        return sameSide(p, a, b, c) && sameSide(p, b, a, c) && sameSide(p, c, a, b);
    }
}

which is to say p is inside triangle abc if it’s on the same side of line bc as a, and on the same side of line ac as b, and on the same side of line ab as c. How obvious is that?

I have to credit my source here, because “point in triangle” was the search that led me to sameSide(): Point In Triangle Test. As it turns out, I haven’t actually used pointInTriangle() for anything yet. behind() and beyond(), though, play an important part in Geometry-Based Path Planning, the subject of a future post.

Frequent sources of inspiration on similar topics are Wolfram MathWorld and of course Wikipedia.


Contents copyright © Alan Shaw 2005-2008

25 queries. 0.336 seconds. Powered by WordPress version 2.5.1