【问题标题】:What happens to a Movieclip when casted as a Sprite?当作为 Sprite 投射时,Movieclip 会发生什么?
【发布时间】:2014-02-13 14:54:28
【问题描述】:

(请注意,我的代码并没有什么真正的“错误”(从某种意义上说,它可以工作),但更想知道它是如何工作的以及引擎盖下发生了什么)

目前我有两个库,每个库都有一个对象。一个设置为“Apple”类,另一个设置为“Pear”,它们位于单独的外部 swfs 中。

Apple 的基类是 MovieClip,就内容而言,它是一个影片剪辑:有帧 + 动画

这是我用来创建和显示 Apple 影片剪辑对象的代码:

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));

上述工作正常,Apple 出现在舞台上并播放。

但是,Pear 的基类是 Sprite(没有动画、帧等)。所以上面的方法失败了,因为该方法应该返回一个 MovieClip。

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

我想了一会儿我必须有上述方法的两个版本,一个用于 Sprite,一个用于 MovieClip。但是只是为了看看,我把它改成了Sprite并尝试创建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);

现在两者都可以工作,但有趣的是,我发现即使该方法返回一个 Sprite,Apple 似乎仍然可以正常工作并在舞台上播放它的动画。我可以将其投射到 MovieClip 并访问所有与 MovieClip 相关的属性等等。

然后我的问题是,当 Apple 作为 Sprite“存在”时,所有与 MovieClip 相关的“东西”发生了什么,这实际上是在必须使用 MovieClips 和 Sprite 时做的正常事情(假装你只仅在需要时才拥有 Sprite 并投射到 MovieClip?)

【问题讨论】:

    标签: actionscript-3 flash


    【解决方案1】:

    Sprite 类提供了比MovieClip 更多的基本功能,但所有属于MovieClip 的内容都可以通过使用Sprite 类的功能进行操作。事实上,如果您的唯一意图是执行addChild(),您的方法可以返回低至DisplayObject 的类。类型转换不会剥离类型转换对象的任何功能,而是会限制对其属性的可用调用。比如说,DisplayObject 具有 xy 属性,Sprite 可用于将对象添加到自身(addChild() 方法),而 DisplayObject 没有,MovieClip 具有内部动画和一个 gotoAndStop() 方法 Sprite 没有。因此,如果您将Apple 类型转换为Sprite,则无法调用引用的gotoAndStop(),因为您已经告诉程序引用只是Sprite。如果您将ApplePear 对象类型转换为DisplayObject,则不能调用其addChild() 方法来说添加健康条(苹果有健康条很奇怪,但为什么不呢?),因为reference 不知道底层对象是否支持此功能。但实际上这两个对象都不会发生任何事情,无论您如何对它们进行类型转换,您只会限制自己通过收到的引用应用更高级的操作。

    实际上,通过类型转换为自己限制功能是一种很好的做法,因为这样可以保护您免于在工作代码上“拐杖”,这可能会破坏其用途。比如说,如果您决定将 Apple 转换为具有定制属性、嵌入式事件侦听器、颜色变化的高级 Sprite 类(例如,public class Apple extends Sprite {...}),您的代码不会感到惊讶,但是如果您将新创建的AppleSpriteDisplayObject,您正在从实例的任何额外功能中抽象出来,并且仅使用其基本接口(类型转换类的属性和方法)来执行Sprite 或@987654350 的操作@ 用于。如果您在高级 Apple 中有一些被覆盖的属性,它们将按照您在创建 Apple 类时的预期工作,即使从超类描述中解决 - 这实际上就是覆盖的用途。

    简而言之,不要担心失去功能,而是尝试将类型转换为使用功能最少的类。

    关于“幕后发生的事情”:每个类都有自己的属性和方法表,如果一个类正在扩展另一个类,则该类的表与超类的表相同,并且占用了额外的空间包含有关类实现的属性和方法的信息。如果有覆盖,则被覆盖的方法的信息会替换表中类的相应方法的信息,而不是超类的信息。每个实例都有一个分配给它的内存块,有一个对类的属性和方法表的引用,如果通过实例引用调用方法,则该引用将用于执行正确的代码。因此,当您从实例调用方法或分配有 getter 或 setter 的属性时,会发生以下情况:

    1. 正确的参数集(包括指向该实例的“this”指针)被压入堆栈。 Flash编译器保证了顺序和参数类型的正确性。
    2. 然后,通过实例的prototype 属性引用实际上是该实例的正确类的类。 AS3 中的每个类都有这个属性。该实例是通过先前压入堆栈的“this”指针访问的。 (从技术上讲,它不仅存储在堆栈中,因此引用与放入堆栈中的引用相同)
    3. 然后,从该表返回正确的偏移量(由编译器确定),即类的调用方法的地址。覆盖与否,不管这里,因为在编译时检测到不正确的覆盖,这里不会发生。
    4. 然后,代码执行转移到返回的地址。然后该方法的代码解析堆栈中的参数,进行更多数据保护并继续执行。

    【讨论】:

    • 我明白了,所以根据我的使用方式,将事情保持在“最简单”的形式实际上是一件好事。有趣的是,您提到了健康栏,因为这是用于游戏的,而我刚刚所做的是从主 fla 中删除所有嵌入的 npc 和项目精灵,以便从单独的 swf 库外部加载(NPC 是动画的,而项目不是)。有助于了解下面发生的事情,也有助于了解未来的最重要的点。谢谢。
    【解决方案2】:

    这里要理解的最重要的一点是对象本身不会发生任何事情,但是编译器对待对象的方式会有所不同

    例如,编译器会将这个函数返回的对象视为Sprite,即使我们实际上返回了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.
    

    所以我们需要在这里做的(正如你目前所理解的那样)是通过类型转换告诉编译器结果是实际上MovieClip

    var test:MovieClip = makeSprite() as MovieClip;
    

    另一件需要注意的事情是,如果你要trace() 结果,你会得到[object MovieClip] 而不是[object Sprite]

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

    使用is 检查是否为MovieClip 将返回true

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

    即使您对test 使用更原始的类型,您的对象也将是真正的MovieClip

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

    【讨论】:

    • 对于编译器告诉我的内容以及它如何处理我的代码与播放器运行时实际发生的情况,我想我有点搞混了。但我想我现在明白了。您使用“is”的第二点以及追踪对象时会发生什么很有趣-不知道它可以这样工作。谢谢。
    【解决方案3】:

    将 MovieClip 投射到 Sprite 不会剥离其 MovieClip 实现;它只是告诉调用代码(在本例中为this.addChild())“这个对象是一个Sprite;请这样对待它。”调用代码不知道——也不关心——它真的是一个 MovieClip,因为只要它是一个 DisplayObject(它是 Sprite 和 MovieClip 的派生对象),addChild() 方法就会很乐意接受它。

    由于MovieClip 无论如何都是Sprite,它对调用代码没有任何影响。不过,就 Apple 对象本身而言,它本质上仍然是一个 MovieClip,因此将继续像一个一样独立运行。

    打个比方,想想去杂货店,买一些商品,然后在柜台付款。你是一个在生活的各个方面扮演着许多不同角色的人,但就收银员而言,你只是一个购物的顾客。这不会让您变得不那么像一个人,但收银员不必对您是谁或您所做的事情感兴趣,而不仅仅是客户进行购买。

    【讨论】:

    • 谢谢,您的类比确实有助于描绘在对象之间的实际交互过程中发生的事情……或者更确切地说是人。
    【解决方案4】:

    TL;DR:

    您的 MovieClip 将保持为 MovieClip,但向下转换为 Sprite 的引用将只能访问 Sprite 可用的方法和变量。

    【讨论】:

    • 哈哈,我确实倾向于写长篇文章/问题,但不确定我用来创建对象的方法是否有影响,因此我将其包括在内。
    • 您的问题可以很简短地解释,也可以对 OOP 实践进行更深入的解释,例如多态性和实际执行强制转换的资源使用等。已经有一些很长的答案,我的是对于那些不在乎它是如何工作的懒惰的人;)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-19
    • 2010-10-07
    • 1970-01-01
    • 2011-09-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多