Access MainTimeline symbol using class-level embed

2019-05-26 10:13发布

问题:

Here's the scenario:

We have a creative team that operates in Flash CS5.5 and produces SWF assets which have both graphics elements and actionscript code in them, and an engineering team which authors .as files and builds "code SWFs". The code SWF files must load or embed the creative assets and interact with the code therein for our apps to function.

For iOS mobile development, there is another consideration - it is not possible to load runtime code in an AIR app packages for iOS because of Apple TOS (see related question). Hence, it is not possible to use a Loader to load SWFs in an iOS environment and retain their code.

Embedding a SWF into an ActionScript file the standard way results in a Loader that loads the embedded SWF directly as bytes. This results in access to the top-level, main timeline as follows:

[Embed(source="embed_test.swf")]
  private var no_aot_support:Class;

public function main():void
{
  var no_ios:* = new no_aot_support();
  addChild(no_ios);
  no_ios.addEventListener(Event.COMPLETE, function():void {
    var timeline:MovieClip = no_ios.getChildAt(0).getChildAt(0);
    trace(timeline.foo); // foo variable exists, output is 'blah'
  });
}

However, this embedding mechanism doesn't work on iOS devices because runtime-code is not allowed per Apple's TOS - all code must be passed through the ahead-of-time compiler (AOTCompiler) at compile-time, and the SWF embedded in this way does not meet this criteria.

Asking around, I found that one can use a class-level embed to get around this, as a class-level embed does go through the compiler and will result in working code under iOS:

package
{
  import flash.display.MovieClip;

  [Embed(source="embed_test.swf", symbol="Symbol1")]
   public final dynamic class anim extends MovieClip { };
}

This works fine, but I don't want to reference a symbol in the SWF, I want the whole swf (aka reference the main timeline), but the following results in a compiler error:

package
{
  import flash.display.MovieClip;

  [Embed(source="embed_test.swf")]
   public final dynamic class anim extends MovieClip { };
}

I've also decompiled the SWF, found the main timeline symbol, and tried this:

package
{
  import flash.display.MovieClip;

  [Embed(source="embed_test.swf", symbol="embed_test_fla.MainTimeline")]
   public final dynamic class anim extends MovieClip { };
}

But the compiler insists this symbol doesn't exist.

Since I have many, many SWF assets, I need a solution that doesn't involve changing my whole workflow (i.e. going into each FLA and making changes). This should be possible, and if not, this is a workflow problem Adobe should address.

So here I am stuck - regular embeds get the MainTimeline but don't work in iOS, class-level embeds work in iOS and can't access the MainTimeline.

Feel free to download my expample project and play with this.

回答1:

Placing objects on the main timeline always produces generated code in an SWF file: The Flash IDE generates all the necessary code for your application to run, that is, it creates an instance of the document class to be placed on the stage at runtime. This, however, is code that can not be accessed, unless the SWF is loaded via a loader object - neither using [Embed], nor generating an SWC will allow it.

I've tried exporting an SWC, because like @32bitkid I believed this to be a way around the restrictions. And as it turns out, you can instantiate the document class, even if you don't explicitly declare one and have the IDE auto-generate it. But alas, all the stage instances are gone. :( You can also declare a custom document class and place your library objects on the stage using addChild() - then it all works fine. But anything you'll do on the main timeline in the IDE, you can't access. Ever.

This makes sense, too: The objects are in fact stage instances placed on the main timeline instance. But once you call new MyDocumentClass(), you are creating a new instance - which does not have any members yet.

The strange thing is, that all of this should be true for any library objects, as well - placing object instances within a library object timeline should also require code generation, shouldn't it? - but it really isn't the same at all: Anything you've placed on the stage in your library object will still exist and be accessible when you call new MyLibraryObject();.

So. The only way I see for your project to work is that you make your designers create a uniquely named "Timeline" MovieClip (or Sprite) in the library of every FLA and export that for ActionScript. (You can't always use "Timeline", because then you'd have naming conflicts when working with more than one SWC) This "Timeline" is then used to place any necessary objects directly on the stage, add frame code, etc. Within it, the designers can work exactly the way they are used to, without any restrictions other than device hardware and available API. The only real difference is that nothing can ever exist on the main timeline of the FLA itself.

You can then use the SWC approach (which is a lot less tedious than manually producing [Embed] code) and instantiate the "Timeline" symbol from within the main application - everything should be right where you need it.



回答2:

I'm posting my own workaround to this problem, in case any happens by this question:

There is a more details response here: Is it possible to embed or load SWFs when making iphone apps (Is it allowed by Apple)

I've created a tool to workaround this issue by merging embedded SWFs into the main SWF. In this way, the embeds are AOT-compiled and converted into objective-c code, so I get an instance of my asset (the main timeline), and the asset's code is properly cross-compiled to work on iOS devices.

It works by using an embed like this:

[Embed(source="GameLevel.swf")]
  private var GameLevel:Class;

public function main():void
{
  var my_level:* = new GameLevel();
  addChild(my_level);
}

In this scenario, if gameLevel.swf has code in it, it typically wouldn't work in iOS, because new gameLevel() would create a Loader and interpret SWF bytecode. But, if you first run the above SWF through my tool called SWFMerge, it will take your embedded SWF and merge it into your root SWF. Then ADT will compile your main swf (including embedded code) into objective-C, it will work on iOS, and note: new gameLevel() now results directly in an instance of your asset - NOT a Loader.

The SWFMerge tool is here:

http://www.onetacoshort.com/temp/SWFMerge_alpha.swf

Let me know in the comments if this workaround works for you or if you have trouble.

I'll add that using a SWC workflow is better and officially supported, but this workaround is great in a pinch when you either don't have the original FLA source file, or simply want to quickly use existing assets with code in them.