0
votes

(Note that there is nothing really 'wrong' here with my code (in the sense that it works), but more wondering on how it is working and what is happening under the hood)

Currently I have two libraries, each with one object. One is set with the class "Apple" and the other is "Pear", They are located in separate external swfs.

Apple's base class is MovieClip as content-wise it is a movieclip: has frames + animation

And here is the code I used to create and display an Apple movieclip object:

function getClip(inputName, spriteLibrary:Loader):MovieClip {
    var aClass:Class = spriteLibrary.contentLoaderInfo.applicationDomain.getDefinition(inputName) as Class;
    return (MovieClip) (new aClass());
}
this.addChild(getClip("Apple", referenceToTheLoadedSwfThatHasAppleInIt));

The above works just fine and Apple appears on the stage and plays.

However, Pear's base class is a Sprite (has no animation, frames, etc). So the above fails, since the method is supposed to return a MovieClip.

this.addChild(getClip("Pear", referenceToTheLoadedSwfThatHasPearInIt));

I thought for a moment I would have to have two versions of the above method, one for Sprite and one for MovieClip. But just to see, I changed it to Sprite and tried to create Apple:

function getClip(inputName, spriteLibrary:Loader):Sprite {
    var aClass:Class = spriteLibrary.contentLoaderInfo.applicationDomain.getDefinition(inputName) as Class;
    return (Sprite) (new aClass());
}
this.addChild(getClip("Apple"), referenceToTheLoadedSwfThatHasAppleInIt);
this.addChild(getClip("Pear"), referenceToTheLoadedSwfThatHasPearInIt);

Now both will work, but interestingly, I found that even though the method returns a Sprite, Apple still seems to work fine and plays it's animation on the stage. I can cast this to a MovieClip and access all the MovieClip related properties and so forth.

My question is then, when Apple "existed" as a Sprite, what happened to all of it MovieClip related "stuff" and is this actually a normal thing to do when having to work with MovieClips and Sprites (by pretending you only have Sprites and cast to MovieClip only when you need it?)

4

4 Answers

2
votes

A Sprite class provides more basic functionality than a MovieClip, but everything that's a MovieClip can be manipulated by using the functionality of a Sprite class. In fact, your method can return as low a class as DisplayObject if your only intention is to do an addChild(). The typecast will not strip the typecasted object of any functionality, it will instead restrict the available calls for its properties. Say, a DisplayObject has x and y properties, a Sprite can be used to add objects to itself (the addChild() method) which the DisplayObject does not have, and a MovieClip has internal animation and a say gotoAndStop() method which a Sprite does not have. So, if you typecast an Apple to a Sprite, you cannot make a call to the reference's gotoAndStop(), because you've told the program that the reference is just Sprite. If you typecast the Apple or the Pear object to DisplayObject, you cannot call its addChild() method to say add a health bar (weird thing for apples to have health bars, but why not?), because the reference does not know that the underlying object supports this functionality. But actually nothing will happen to either object, no matter how you typecast them, you will just restrict yourself from applying more advanced actions via received reference.

Actually it's good practice to limit the functionality for yourself via typecasts, because you are then protected from making "crutches" over working code which can probably spoil its purpose. Say, your code will not be surprised in case you would decide to turn Apple into an advanced Sprite class (say, public class Apple extends Sprite {...}) with tailored properties, embedded event listeners, color-changing maybe, but if you typecast the newly created Apple to Sprite or DisplayObject, you are abstracting from any extra abilities of the instance and only use its basic interface (properties and methods of a typecasted class) to perform the actions the Sprite or DisplayObject are intended for. If you have some overridden properties in the advanced Apple, they will work as you have intended while making the Apple class, even if addressed from the superclass description - this is actually what override is for.

In short, don't worry about losing functionality, but try typecasting to least possible class with used functionality.

About "what happens under the hood": Each class has its own table of properties and methods, if a class is extending another class, that class's table is identical to the superclass's up to its end, and the extra space is occupied with information on properties and methods implemented by the class. If there is an override, the overridden method's info replaces the info of the corresponding method in the table for the class, not for the superclass though. Each instance has a memory block allocated to it, there is a reference to the class's properties and methods table, which is then used to execute correct code, if a method is called via instance reference. So, when you call a method, or a property with a getter or setter assigned to it, out of an instance, the following happens:

  1. The correct set of parameters, including "this" pointer to that instance, is pushed into the stack. The correctness of the order and the type of parameters is ensured by Flash compiler.
  2. Then, the class that's actually the proper class of that instance is referenced via prototype property of the instance. Every class in AS3 has this property. The instance is accessed via "this" pointer previously pushed into the stack. (Technically it's stored in more than just stack, so the reference is just the same that's put in the stack)
  3. Then, the correct offset (determined by the compiler) is returned from that table, that is the address of the called method of the class. Overridden or not, no matter here, because an improper override is detected at the compile time, and will not happen here.
  4. Then, code execution is transferred to the returned address. The method's code then parses the parameters in stack, does some more data protection and proceeds with its implementation.
2
votes

The most important thing to understand here is that nothing happens to the object itself, but the way the compiler treats the object will differ.

For example, the compiler will treat the object returned by this function as a Sprite, even if we actually return a MovieClip:

function makeSprite():Sprite
{
    return new MovieClip();
}

var test:MovieClip = makeSprite();
// 1118: Implicit coercion of a value with static type flash.display:Sprite
// to a possibly unrelated type flash.display:MovieClip.

So what we need to do here (as you understand currently) is tell the compiler that the result is actually a MovieClip via typecasting:

var test:MovieClip = makeSprite() as MovieClip;

Another thing to take notice of is that if you were to trace() the result, you would get [object MovieClip] rather than [object Sprite]:

trace( makeSprite() ); // [object MovieClip]

And using is to check if it's a MovieClip will return true:

trace( makeSprite() is MovieClip ); // true

Even if you used a more primitive type for test, your object would truly be a MovieClip:

var test:Object = makeSprite();
trace(test); // [object MovieClip]
2
votes

Casting a MovieClip to a Sprite doesn't strip it of its MovieClip implementation; it simply tells the calling code (in this case, this.addChild()) that "this object is a Sprite; please treat it as such." The calling code doesn't know — or care — that it's really a MovieClip, because as long as it's a DisplayObject (which a Sprite, and in turn a MovieClip, derives from), the addChild() method will happily accept it.

Since a MovieClip is a Sprite anyway, it doesn't make any difference to the calling code. As for the Apple object itself, though, it's still a MovieClip at heart, and so will continue to function on its own like one.

As an analogy, think of going to the grocery store, buying some goods, and paying at the counter. You're a person with many different roles in various aspects of life, but as far as the cashier is concerned, you're just a customer making a purchase. That doesn't make you any less of a person, but the cashier doesn't have to be interested in who you are or what you do, beyond just a customer making a purchase.

1
votes

TL;DR:

Your MovieClip will stay a MovieClip, but the reference that is down casted to a Sprite will be able to access only methods and variables that are available to Sprite.