ふと思い立って,プログラマーから見た Flash について書くことにしました。SWF and AMF Technology Center | Adobe Developer Connection からダウンロードできる SWF file format specification をもとに*1。
といっても,ActionScript 1.0 / 2.0 / 3.0 の違いについて説明するものではありませんよ*2。
SWF ファイルフォーマット
ヘッダのあとに,複数の「タグ」と呼ばれる構造体が続く構造になっています。「タグ」という言葉は紛らわしいので以降はチャンク(chunk)と呼ぶことにします。
ある SWF ファイルの例*3を示します。ヘッダは省略しています。
- [D]
DefineBitsJPEG2(ビットマップの定義) - [D]
DefineShape(シェイプの定義) - [D]
DefineSound(サウンドの定義) - [D]
DefineFont(フォントの定義) - [C]
PlaceObject(シンボルの配置) - [D]
DefineText(静的テキストの定義) - [C]
PlaceObject(シンボルの配置) - [C]
ShowFrame(フレームの表示) - [D]
DefineMorphShape(シェイプトゥイーンの定義) - [D]
DefineButton2(ボタンの定義) - [C]
StartSound(サウンドの配置) - [C]
PlaceObject(シンボルの配置) - [C]
RemoveObject(シンボルの削除) - [A]
DoAction(フレームアクション) - [C]
ShowFrame(フレームの表示)
おおまかにいうと,
- [D] シェイプ,テキスト,ビットマップ,サウンドといった「シンボル」の「定義」をするチャンク
- シンボル ID あり(Dictionary に登録される)
- [C] シンボルの表示やフレームのラベル定義など「制御」をするチャンク
- シンボルを表示したり削除したりする Display List tags
- 背景色を指定したり外部アセットを読み込んだりする Control tags
- [A] フレームアクションを示すチャンク
- 厳密には「制御」用チャンクに分類されるが,あえてわけた
にわけられます。また,上記ではメインタイムラインの処理しか記述していませんが,DefineSprite というチャンクで MovieClip を定義することが可能です。この Sprite のチャンクの中には制御用チャンク(やフレームアクション)を入れ子のように含むことができます(末尾ファイル例を参照)。
以下,注意書き。
- 制御用チャンクで利用されるシンボルは,事前に定義用チャンクにより定義されている必要がある。前方参照はできない。
- 前方参照を許さない構造のため,Flash Machine(ActionScript VM ではない)は,ファイルを頭から読み込んでシーケンシャルにレンダリングしていけばよい。
- このため HTTP Stream など遅いストリーム上の SWF ファイルを,順次レンダリングしていくことが可能である。
- レスポンス,インタラクティビティの向上
- 「レイヤー」は Flash IDE が自動的に depth を割り振っているだけである。
- モーショントゥイーンやイージングは,専用タグ(命令)があるわけではなくて,各フレームごとに Transformation Matrix を指定した
PlaceObjectがある(Flash IDE が各フレームに分解している),と思われる。
何がいいたいかというと,Flash IDE での操作から見える構造(FLA ファイル)というのは IDE 上の表現であり,SWF 上ではより「低レベル」の表現がされている,ということです。
SWF action model の変遷
原典の「Actions」章を参照してください。
SWF 3 action model
- まだ「ActionScript」とはいえない(基本的に MovieClip 操作命令だけ)
Play,Stop,GotoFrame等最小限の「命令」のみSetTargetによって action の対象を切り替えることができるだけ
たとえば button over などのイベントハンドラはどのようになっているのかというと,DefineButton2 チャンクに該当するイベント用の ACTIONCODE が含まれています*4。
SWF 4 action model
Push,Pop,Add,Equals,And,SetVariable等,スタックマシン型言語処理系として最低限の ACTIONCODE が定義された- フロー制御は
Jump,If,Callのみ- つまりユーザー定義関数(・プロシージャ)の呼び出しのみ可能
Flash Lite 1.x のベースとなっています。
SWF 5 action model
CallFunction,CallMethod等によって「名称ベース」の動的バインド呼び出し((OLE automation 的というか……いや別にDISPIDをあらかじめ取得しなきゃいけないわけじゃないですが。))ができるようになった- つまり,実行系で定義されている複雑なクラス・関数(たとえば
MovieClip.beginFill()とか)をも call できるようになった
- つまり,実行系で定義されている複雑なクラス・関数(たとえば
NewObject,GetMember,Enumerate等によって,「クラス」概念をサポートした- プロトタイプベースのオブジェクト指向(JavaScript と同じ)
Subclass.prototype = ...
DefineLocalによってローカル変数,グローバル変数の別ができた- VM として配列をサポートした*5
- 文字列定数プールが定義できるようになった
Incrementやビット演算など,いくつかの便利な演算用 byte code が実装された(FlashLite 1.x では一部実装済み)
SWF 6 action model
- これまで action chunk を定義していた
DoActionに加えて,DoInitActionでも action chunk を定義できるようになったDoActionがフレーム内に DisplayList を配置してから処理を開始していたのに比べ,DoInitActionは到達した瞬間に実行される(ただし初回のみ)
Flash Lite 2.x のベースとなっています。
SWF 7 action model
Extendsによって単一ステップによって継承を表現できるようになった- これまではコンパイラががんばっていたところを,AVM が裏側でやるようになっただけ?
CastOpによってクラス(・インタフェース)間キャストができるようになった?ImplementsOpによってインタフェースをサポートした?Try,Throwによって例外処理できるようになった
SWF 9 action model
DoABCチャンクによる AVM2 用バイトコードのサポート
action model の変遷のまとめ
以下のように,だいたい3回の大変革がなされています。
| SWF 3 | 単純なフレームアクション |
| SWF 4 | 単純な演算が可能なスタックマシンとしての実装 |
| SWF 5 | 外部関数(システム組込関数)の動的バインド呼び出しのサポート,クラスのサポート |
| SWF 9 | AVM2 のサポート |
とくに SWF 5 における「外部関数の動的バインド呼び出し」が大きいです。SWF 4 までは ActionScript のみで Flash movie を作成するのは事実上不可能でしたが,SWF 5 では「定義用チャンク」に相当するクラスが Flash Machine に準備されたため,これを外部関数・クラスとして呼び出すことにより ActionScript のみでかなりの Flash movie を作成することができるようになりました。といっても,ビットマップやフォントについては ActionScript でイチからどうこうできないため((フォントはともかくビットマップレベルであれば flash.display.BitmapData を利用すれば描けないことはありませんけど。)),リソース埋め込みなどの外部ツールが必要になります。
逆にいうと,SWF 4 までであれば Flash file format specification を読みこなせば自作 Flash Player を作成することができましたが,SWF 5 以降は(specification に記述されていない)Flash Machine 組み込みクラスを実装する必要があります。
さらに付け加えると,ActionScript VM は byte code interpreter として働いているだけであり,ソースとなる言語の文法を規定しません。実際,ActionScript 1.0 から 2.0 では文法的に大幅な改変がなされましたが,byte code compiler の部分で吸収しています*6。また,haXe は型パラメータ*7をもつなど ActionScript 2.0 とは言語仕様に異なる部分がありますが,これも byte code を経由しているおかげです。
SWF ファイル生成のためのツール
だいたい挙動を把握しているものだけをあげておきます。
- Flex SDK
- Adobe 製
- written in Java?
- えーっとどう説明すればいいんですかね?
- 少なくとも ActionScript コンパイラとしては働くといえる
- mxml をどう SWF に落としているかはわかんない
- MTASC
- ActionScript 2.0 互換? ActionScript コンパイラ
- written in OCaml
- これ単体だと ActionScript のコンパイルしかしない(
DoAction,DoInitAction,ShowFrameのチャンクしか吐いていない気がする) - byte code optimization をしているかは不明(OCaml 読めないんですもの); してない気がする。
- haXe
- ActionScript 2.0 的 + 独自仕様 ActionScript コンパイラ
- written in OCaml
- MTASC と同じベンダの後継ツール
- これ単体だと ActionScript のコンパイルしかしない
- AVM1 byte code のほかに,下記のものにトランスレートできる
- AVM2 byte code
- ActionScript 3.0 source
- JavaScript source
- PHP source
- Neko VM 用 byte code
- byte code optimization をしているかは不明; してない気がする。
- libming
- C API(ならびに他言語バインディング)により SWF を各チャンクとして生成するもの
- written in C
- ActionScript compiler 部は,lex, yacc 利用
- これだけで SWF ファイルを生成可能(フォントまわりは独自仕様ですが)
- byte code optimization はしてない
先に述べたように,SWF 5 以降では Flash Machine のクラスを呼び出せるようになったので,ActionScript を MTASC や haXe でコンパイルし,スクリプトでは記述できない画像やフォントを swfmill で定義用チャンクとして埋め込む,という手法が流行っているようです。swfmill でシェイプを定義することも可能ですが,XML でシェイプを描いていくより ActionScript で描くほうが面倒くさくないし柔軟性もありますからね。
FlashLite 1.x は SWF 4 ベースなので ActionScript でシンボルの定義・生成が行えないため,サーバサイド SWF 生成の分野では libming + 他言語バインディングが利用されているようです。ActionScript メインで使うのなら個人的に libming はおすすめしません*9。
SWF ファイルの詳細例
実際にはテキスト形式になっているわけではありませんが,「構造」を疑似言語で示しました。
HEADER {
Signature: CWS;
Version: 6;
FileLength: 1234;
FrameCount: 100;
...
}
chunk "DefineBitsJPEG2" as character(1) {
JPEGData:
0xff, 0xd8, ......
}
chunk "DefineShape" as character(2) {
ShapeBounds: (0,0)-(100,100);
Shapes:
[ SHAPE { ... }, ... ];
}
chunk "DefineSound" as character(3) {
format: MP3;
rate: 44kHz;
data:
0xff, 0xaa, 0x90, 0x40, ......
}
chunk "DefineFont" as character(4) {
glyph[0]:
SHAPE { ... };
glyph[1]:
SHAPE { ... };
...
}
chunk "PlaceObject" {
place character(2) on depth(10)
with matrix { 1, 0, 0, 1 };
}
chunk "DefineText" as character(5) {
bound: 0, 0, 300, 40;
TextRecords:
[ Font(1001).glyph[0], ... ];
}
chunk "PlaceObject" {
place character(5) on depth(20)
with matrix { 1, 0, 0, 1 };
}
chunk "ShowFrame";
chunk "DefineMotionShape" as character(6) {
StartBounds: (0, 0)-(100, 100);
EndBounds: (0, 0)-(100, 100);
MorphFillStyles:
FillStyles: ...;
......
}
chunk "DefineButton2" as character(7) {
Characters:
BUTTON { character(1), ... }
Actions:
on over {
SetTarget "spinner"
GotoFrame(0);
SetTarget "";
}
}
chunk "StartSound" {
play(3)
with loop(5) from ...;
}
chunk "PlaceObject" {
place character(7) on depth(30)
with matrix { ... };
}
chunk "RemoveObject" {
remove character(2) on depth(10);
}
chunk "DoAction" {
GotoFrame(1);
}
chunk "ShowFrame";
...
chunk "DefineSprite" as character(100) {
chunk "StartSound" {
play(53);
}
chunk "PlaceObject" {
place character(54) on depth(10)
with matrix { ... };
}
chunk "ShowFrame";
chunk "PlaceObject" {
place character(58) on depth(20)
with matrix { ... };
}
chunk "RemoveObject" {
remove character(54) on depth(10);
}
chunk "DoAction" {
GotoFrame(1);
}
chunk "ShowFrame";
}
chunk "PlaceObject" {
place character(100) on depth(10)
with matrix { ... };
}
chunk "ShowFrame";
...
chunk "End";
*1:かつては Macromedia のややこしい制限(→DSAS開発者の部屋:SWFファイルフォーマットとライセンス)に配慮したり,unofficial な SWF File Format Reference | Made to Order Software Corporation で勉強したりしなくてはいけませんでした。いい時代になったものです。
*2:Adobe の文書だと AVM の違いと言語仕様の違いがごたまぜになっている印象があります。ニワカには 2.0 と 3.0 の言語仕様の違いがわかりませんでした。
*3:実在するものではありません。原典「Introduction」章の「The dictionary」節の例をやや改変しています。
*4:これは SWF 4 action model でも同様です。SWF 5 以降でも同じですが,SWF 5 以降はインスタンスのイベントハンドラプロパティとして ActionScript からアクセス可能になります。
*5:SWF 4 でも変数名を工夫することにより,無理やりハッシュ的なものを使うことはできましたが。
*6:もちろん新文法・構造をサポートするため追加された byte code が存在します。SWF 7 action model など。
*7:C++ の template 機構のように働くことができます。
*9:と書くと FUD みたいですね。libming はどちらかというと API によるチャンクの生成がメインであり,ActionScript compiler はおまけでしたから,私感として質がどうもなぁ。