AS2でリンケージ識別子が衝突する時のまとめ
2009 年 10月 30 日 金曜日 kosuke先日、仕事でハマったところのまとめ。
僕の場合、普段は親SWFから子SWFを読み込んだら不用になったタイミングでアンロードします。その方が無駄にメモリー食わないとでしょうし。
しかし、コンテンツ毎に読み込むような、子SWFがいくつもあったりする場合、毎回ローディングするのが鬱陶しい…とか、サーバー負荷が…とかの理由でアンロードしない仕様で作る場合もあったりする。
この時、複数のSWFに同名のリンケージ識別子があると衝突することがあります。
AS2の場合、この回避には識別子をユニークにするしかない(であってる?)と思います。その挙動を記録しておきます。
上記の検証ファイルは、親SWFのindex.swfから、
子SWFのcontent1.swfとcontent2.swfを読み込んで試したもの。
子SWFとなる二つのファイルには、同じリンケージ識別子Mainで、それぞれのクラスを定義した、色違い・マウスアクション違いの矩形をアタッチしています。
content1.swfとcontent2.swf
public var main:MovieClip; public function onLoad():Void{ //Mainをアタッチ this.main = attachMovie( "Main", "main", this.getNextHighestDepth() ); } |
content1.swfのMain。
public var field:TextField; //マウスプレスで「1」を表示 public function onPress():Void{ this.field.text = "1"; this.field._alpha = 100; Tweener.addTween( this.field, { _alpha:0, transition:"easeNone", time:1 } ); } |
content2.swfのMain。
public var field:TextField; //マウスプレスで「2」を表示 public function onPress():Void{ this.field.text = "2"; this.field._alpha = 100; Tweener.addTween( this.field, { _alpha:0, transition:"easeNone", time:1 } ); } |
親SWFの各ボタンは、load、unload、子がアタッチしたMainインスタンスをremoveするボタンを設置。
//content1.swfをロード public function loadContent1():Void{ if( this.content1.main.getBytesLoaded() ) return; //ロード前ならロード if( !this.content1.getBytesLoaded() ){ this.loader.loadClip( "content1.swf", this.content1 ); } //ロード済みの場合onLoad実行 else{ trace("loaded"); this.content1.onLoad(); } } //content2.swfをロード public function loadContent2():Void{ if( this.content2.main.getBytesLoaded() ) return; //ロード前ならロード if( !this.content2.getBytesLoaded() ){ this.loader.loadClip( "content2.swf", this.content2 ); } //ロード済みの場合 else{ trace("loaded"); this.content2.onLoad(); } } //content1.swfアンロード public function unloadContent1(){ this.content1.unloadMovie(); } //content2.swfアンロード public function unloadContent2(){ this.content2.unloadMovie(); } //content1.swfがアタッチしたmainを削除 public function removeContent1(){ this.content1.main.removeMovieClip(); } //content2.swfがアタッチしたmainを削除 public function removeContent2(){ this.content2.main.removeMovieClip(); } |
これを使って、
Content1LoadクリックしてContent2Loadクリックすると、子SWFのMainが表示されます。表示された矩形をクリックすると左側は「1」右側では「2」が表示されます。
続けて、Content1Removeをクリック。左側のmainがremoveされる。
再度、Content1Loadをクリック。再び左側のmainがアタッチされて表示される。
左側のmainをクリックすると、今度は「1」のはずが「2」と表示されます。
removeでは、読み込んだ子のSWF自体をunloadしていません。この時同じリンケージ識別子があると再アタッチの際、あとで設定されたリンケージ識別子のクラスが適用されるみたい。シンボル自体は正しく表示されるのだけれど。
この症状はunloadをした場合とか、上書きで読み込んだ場合等は発生しないです。
Content1Unloadをクリック。Content1Loadをクリックした場合、正しく「1」が表示されます。
再アタッチしなければ問題なさそうな気もする。
とにかくリンケージ識別子のネーミングには気をつけよう。
というか僕的にはunloadしたい。
読み込んだものを消さない仕様の場合(に限らないが…)、複数人で制作する場合って、ある程度、親子それぞれの仕様とかネーミングルールとか気をつけて決めないと結合した時に面倒なことになりそうですね。
Logic Express 9 買った
2009 年 10月 25 日 日曜日 kosukeAS3によるサウンドライブラリFLMMLやSiONを触っていたら、DTM熱が沸いてつい買ってしまったLogic Express 9。よせばいいのに…俺。
きっかけは、先のSiONを試そうと文法を学びつつMMLを打っていたはずが、音が出るのが楽しくなってきて、いつの間にか好きな曲の耳コピーになり、DominoやGarageBandでピアノロールと格闘することに…。GarageBandに触発された時は三日坊主だった音楽アプリですが今回は結構夢中になってました。
そうするとDominoやGarageBandでやりたいことは出来るのだけど、Dominoは慣れないWindows環境、GarageBandはMIDI書き出しが出来ない(MIDI書き出ししたいのは、あとでMIDIをMMLに変換かけたいから。もともとはFlashの手法として活用するのが目的だし。)のがどうも気になってきて、どうせ新しく覚えるなら今後も使うアプリにしようと思ったわけ。
GarageBandでMIDI書き出しが出来れば、DTMシロウトの僕にはそれで充分だったんですけどね。
僕の周りはWeb関連の仕事をしている人がほとんどで、DTMに詳しい人はいませんが案外DTMアプリ持っていたりする。やってみたくなるんだよなぁ。音って。誰にも直感的だしね。
周りはReason使ってる人が多い。
DTMのアプリは、PhotoShopとかIllustratorみたいな「超定番」ってない感じだよね。3Dもそんな感じか。選べるってことは素晴らしいが悩む。
僕はReasonかCubaseにしようかなぁと思ったけど、最終的にGarageBandからの流れでLogic Expressにした。さすがにLogic Proは高くてろくに使わなかった時、後悔する可能性があるんで。
僕の音楽の知識は昔、ギターやってましたって位。
アプリがあったからって一朝一夕で出来ることじゃないでしょうけど何かに役立つといいなぁ。
たまには別のこともやってみたいみたいな。自分的にはそんな感じです。
Progression4 LoopListを試してみた
2009 年 10月 23 日 金曜日 kosukeProgression4に追加された新しいコマンドリストの一つにLoopListがあります。
LoopListは名前の通り、登録されたコマンドを繰り返し処理するコマンドリストです。
これを試していてProgression4で変わったことも気づいたので記録します。
LoopListはループの回数をrepeatCountで指定出来ます。
上のサンプルだと、indexSceneでは、
this.addCommand( new LoopList( 2, null, new DoTweener( this.logo, { scaleX:0.5, scaleY:0.5, transition:"easeNone", time:0.25 } ), new DoTweener( this.logo, { scaleX:1, scaleY:1, transition:"easeNone", time:0.25 } ) ) ) |
Scene1では、
this.addCommand( new LoopList( 2, null, new DoTweener( this.logo, { scaleX:2, scaleY:2, transition:"easeNone", time:0.25 } ), new DoTweener( this.logo, { scaleX:1, scaleY:1, transition:"easeNone", time:0.25 } ) ) ) |
で、それぞれ2回繰り返しを指定しシーン遷移時に拡大縮小しています。
これは問題ないと思う。
注意すべきは、コマンドを単体で使う時。
Scene2のLoopListは、ナビゲーションから開始や停止できるようにしています。
this.t0 = new DoTweener( this.logo, { x:520, transition:"easeNone", time:0.5 } ); this.t1 = new DoTweener( this.logo, { x:120, transition:"easeNone", time:1 } ); this.t2 = new DoTweener( this.logo, { x:320, transition:"easeNone", time:0.5 } ); this.comm = new LoopList( 2, null, this.t0, this.t1, this.t2 ); |
ここではシーン遷移時に処理しているのではなくて、LoopListのインスタンスをマウスイベントで実行したり停止したりしています。
注意すべきは、LoopListを実行して停止してもループ回数を数えるcountは0に戻らないという点。
なので、Scene2でexecuteボタンを押すと、初回は2回ループで停止しますが再度executeを実行してからは無限ループになります。止めるにはstopを使う。
countは読み取り専用で書き込めないのと、カウンタを戻すresetメソッドはprotectedで宣言されている為countを戻すのは、あらかじめLoopListを継承したカスタムコマンドを使うとかかなぁと。
stopメソッドで止めた時と、interruptメソッドで中断した時の挙動の違いも確認。
stopで止める時は、処理中のコマンドが終わった時点でループが停止になる。
停止後に再度executeした時も、その次のコマンドから実行されています。
対して、interruptで中断した時は他と同様にコマンド中断処理が実行されます。
再度executeした時も、中断処理後そのままならループの最初のコマンドから実行される。この時もcountは0なっていない。
またProgression3と違って、コマンド中断時の処理方法をinterruptTypeで指定できる。
CommandInterruptTypeクラスに定数が用意されていて、
- ABORT:int = 1
コマンド中断時、その時点の状態で停止するように指定します。 - RESTORE:int = 0
コマンド中断時、処理が実行される以前の状態に戻すように指定します。 - SKIP:int = 2
コマンド中断時、処理が完了された状態と同様になるように指定します。
のようになっています。デフォルトはSKIPみたい。
これまで、エラーをキャッチしてexecuteCompleteとかやっていたのをこれだけで処理できるわけです。
中断した時のループ位置をとって、そこからループ再開させたりとかややこしいことをしたい場合は、イベント発生をキャッチして処理しなきゃいけないですが、大抵の場合、このinterruptTypeのパターンで済むんじゃないですかね。
上のサンプルでは、コンボボックスでLoopListに登録しているDoTweenerのinterruptTypeを変更出来るようにしています。挙動の違いが確認できると思う。
やっぱり便利になってるなぁ。
Progression4 Bata SceneLoaderを試してみた
2009 年 10月 14 日 水曜日 kosukeProgression4 Bata、粛々と試しています。
目玉機能のSceneLoaderを試してみたのだけど、SCENE_POST_UNLOADイベントの挙動で悩んでいる。
API Referenceによれば、SceneLoaderがSWFを読み込み済みで、シーン移動時の目的地がシーンオブジェクト自身、または親階層の時にSceneEvent.SCENE_UNLOADが発生した時、atSceneUnloadが動くはずだと思う。
これを前提に試してみたものは、
indexScene以下に、4つのSceneLoaderを作って別々のSWFを読み込ませている。
SceneLoaderのatScenePreLoadでローディングバーを表示している。
SceneLoaderのatSceneUnloadでunloadを実行しているのだけれど、親に戻る時(Progression4Bataの文字を押した時)はもれなくSCENE_POST_UNLOADイベントが発生して、atSceneUnloadが動いている。ここまではOK。
問題は別のSceneLoaderに移動した時、atSceneUnloadが動いたり動かなかったりする。
atSceneUnloadが動かなければunloadがされないので、もう一度同じSceneLoaderに移動した時は、atScenePreLoadは動かないのでロディングバーは表示されない。これは期待通り。
Flashの出力を見てもscenePostUnloadが出力されないのだが、なんでなんだろう。
ズームのブラー
2009 年 10月 13 日 火曜日 kosukeこちらは作ってみたけど、重くて使えなかったという代物。残念な話が続くなぁ。
ズームのブラー表現する場合って、どうしたって繰り返し拡大したイメージを重ねるしかないのかなぁ。
何かあったら教えて欲しいです。
var blur:BlurZoom = new BlurZoom( ズームをかけたい対象, ズーム, オフセットX(0.5が中心), オフセットY(0.5が中心), クオリティ ) |
で適用します。
重いですがどうぞ。
画像が小さければギリギリ使えるかも。
package jp.nipx.effect{ import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.DisplayObjectContainer; import flash.geom.Matrix; import flash.geom.Rectangle; import flash.display.PixelSnapping; import flash.geom.ColorTransform; public class BlurZoom extends Bitmap{ public function BlurZoom( container:DisplayObjectContainer, zoom:int=0, offsetX:Number=0.5, offsetY:Number=0.5, quality:Number=5 ){ super( new BitmapData( 1,1 ), PixelSnapping.AUTO, true ); this._rect = new Rectangle(); this.container = container; this._zoom = zoom; this._offsetX = offsetX; this._offsetY = offsetY; this._quality = quality; this.render(); } public var container:DisplayObjectContainer; private var _rect:Rectangle; private var _distance:Number = 0; private var _zoom:int; public function get zoom():int{ return this._zoom; } public function set zoom(n:int):void{ this._zoom = n; this.render(); } private var _offsetX:Number; public function get offsetX():Number{ return this._offsetX; } public function set offsetX(n:Number):void{ this._offsetX = n; this.render(); } private var _offsetY:Number; public function get offsetY():Number{ return this._offsetY; } public function set offsetY(n:Number):void{ this._offsetY = n; this.render(); } private var _quality:Number; public function get quality():Number{ return this._quality; } public function set quality(n:Number):void{ this._quality = n; this.render(); } public function render():void{ if( this.container.contains( this ) ) this.container.removeChild( this ); if( this._zoom == 0 ) return; var rect:Rectangle = this.container.getRect( this.container ); var width:Number = rect.right - rect.left; var height:Number = rect.bottom - rect.top; if( this._rect.equals( rect ) ){ this.bitmapData.fillRect( this._rect, 0x00000000 ); } else{ this.bitmapData.dispose(); this.bitmapData = new BitmapData( width, height, true, 0x00000000 ); var a:Number = Math.sqrt( Math.pow( width, 2 ) + Math.pow( height, 2 ) ); var b:Number = a + 1; this._distance = b / a - 1; } var tx:Number = ( 0<rect.left ) ? 0 : -rect.left; var ty:Number = ( 0<rect.top ) ? 0 : -rect.top; this.bitmapData.draw( this.container, new Matrix( 1, 0, 0, 1, tx, ty ) ); for( var i:int=1; i<this._zoom; i++ ){ var s:Number = 1 + ( this._distance * this._quality ) * i; var x:Number = ( width * s - width ) * this.offsetX; var y:Number = ( height * s - height ) * this.offsetY; this.bitmapData.draw( this.container, new Matrix( s, 0, 0, s, tx - x, ty - y ), new ColorTransform( 1, 1, 1, 1/this._zoom ), null, null, false ) } this.x = -tx; this.y = -ty; this.container.addChild( this ); } } } |
ライフライン
2009 年 10月 13 日 火曜日 kosukeProgression 4 Bata のことはじめ
2009 年 10月 12 日 月曜日 kosukeProgression4のBata版が公開されて少し起ちますが、先日、試しにとあるProgression3プロジェクトをProgression4に書き換えてみました。
といっても、純粋にProgression3の構文をProgression4で動くように書き換えただけですので、まだProgression4の新機能はほとんど試せていません。
Progression3で出来ていたことを、Progression4にそのまま移行すること自体は問題なく出来る感じです。
以下、細かい感じですが、今のところ迄で気づいたことを記録しておきます。
■ブラウザウインドウ最小サイズの管理がSWFForceSizeからSWFSizeに変わったみたい。
SWFSizeはSWFForceSizeの機能をASに埋め込んでFlashからHTMLの最小サイズ、最大サイズを設定できるライブラリ。
Progression4 Bataでは、このライブラリが採用されている模様。
Progression3では、HTMLへの記述で最小サイズを指定していました。
progression.embedSWF( { width:800, height:600, adjustHorizontal:true, adjustVertical:true, centering:true, flashvars:{ }, params:{ bgcolor:"#000000", wmode:"window", allowscriptaccess:"samedomain" }, attributes:{ } } ); |
このwidthとheightで最小サイズが設定されていたのですが、Progression4 BataだとWebConfig()を生成した段階なんかで設定されるようだ。
WebConfigの第二引数でSWFSizeを有効化するかどうかとなっている。
で、このまま何もしないとflaファイルのステージサイズが最小サイズになる模様。
またPreloaderを使っている場合、Preloaderの段階では最小サイズの制限が設定されない。
なので、全画面Flash、最小サイズ固定で作る場合、Preloaderの段階で明示的にSWFSize.initializeを実行しておいたほうが良さそう。
■Listenコマンドが無くなったみたい
ListenコマンドはProgression3にあったイベント待ちのコマンド。
あまり使うこともないと思いますが、Listenと同様のことをするならFuncコマンドを使う感じですかね。
protected override function atSceneLoad():void { var dispatcher:SceneObject = this; addCommand( new Trace( "1st" ), new Func( function():void{ Tweener.addCaller( this, { count:1, delay:2, onComplete:function():void{ dispatcher.dispatchEvent( new Event( Event.COMPLETE ) ) } }); }), new Func( new Function(), null, dispatcher, Event.COMPLETE ), //Listenコマンド代用 new Trace( "2nd" ), new Func( function():void{ Tweener.addCaller( this, { count:1, delay:2, onComplete:function():void{ dispatcher.dispatchEvent( new Event( Event.COMPLETE ) ) } }); }), function():void{ this.listen( dispatcher,Event.COMPLETE ) }, //匿名関数でListenコマンド代用 new Trace( "finish" ) ); } |
上記の場合はそもそもListenでやらず、Tweener.addCallerの書かれたFuncでイベント待ち設定すれば良い訳だけれど。
■コマンドの処理状態プロパティがrunningからstateに変わったみたい。
Progression3では、コマンド処理中であるかをどうかを、runningで判定していましたが、これがstateプロパティに変わった模様。Progression3での中断処理のプロパティinterruptingと一緒にstateに統合された感じかなと。
■Progressionのオブジェクトを継承したSWFを読み込んでもガベージコレクションされるみたい。
問題になっていたメモリリークも大分解消されているっぽい。
以前メモリリークで試したサンプルをProgression4に書き換えてみたのがこちら。
サンプル:Progression4 Bataのメモリ使用状況確認
最初の読み込み分が消えいないような気がしますが、ひたすら増えてくってことは無くなった模様。
読み込まれるSWFの方にシーンを持たせるって、目玉の機能はまだ試せていないので、その時のメモリの挙動も次試してみようと思ってます。
ベータ版の状況なんで、今後どう変わってくるかわからないですが正式版のリリース心待ちにしてます。