Adobe AIR Galleryに載りました。
2009 年 12月 18 日 金曜日 kosukeFlowerWallPaper、Adobe AIR Galleryに登録しました。
登録から2週間、載せてもらえないのかと思ったよ。よかった。
AMFのこと
2009 年 12月 9 日 水曜日 kosukeairアプリを作った際、その初期設定を保存するようにしました。
その保存フォーマットはオブジェクトをJSONでエンコードしてテキスト形式を使っていたのだけど、良くみてみるとActionScriptではAMFってフォーマットがある。
これを使えばActionScriptで生成したオブジェクトを丸ごと保存するってことが出来そうだ。
とりあえず、見つけた資料はこのあたり。
AS2.0までに対応する規格AMF0とAS3.0以降に対応する規格AMF3がある。
AMF 0 規格仕様
AMF 3 規格仕様
ググった感じサーバーとのやり取りに使うケースが主目的っぽいのと、なぜMovieClipは渡せないの?(上の規格資料だとAMF0規格時からサポート外となってるっぽい)とかまだ不明なところがあるのだが、今回のような目的ならJSONで保存するより便利そうと気づいた点が2点あります。
まず、JSONでうまくエンコードできなかったSpriteなどのオブジェクトもそのまま保存出来た。
以下のようなSpriteを含むオブジェクトをJSONでエンコードした場合(エンジンはas3corelibで試してます。)エラーになったけど、AMFではそのまま保存出来る。
次にAMFにある型のオブジェクトは、そのままの型で受け取れる。
var file:File; var stream:FileStream = new FileStream(); stream.objectEncoding = ObjectEncoding.AMF3; var read:Object var sp:Sprite = new Sprite(); sp.x = 10; //保存するオブジェクト var write:Object = { arr:[ "a", "b", "c" ], date:new Date(), xml:<root>root</root>, sprite:sp, byte:new ByteArray() } //AMF------------------------------------------ file = File.desktopDirectory; file = file.resolvePath( "AMF.dat" ); stream.open( file, FileMode.UPDATE ); stream.writeObject( write ); stream.position = 0; read = stream.readObject(); trace( "AMF:", read.arr, read.arr is Array ); trace( "AMF:", read.date, read.date is Date ); trace( "AMF:", read.xml, read.xml is XML ); trace( "AMF:", read.sprite.x, read.sprite is Sprite ); trace( "AMF:", read.byte, read.byte is ByteArray ); file.clone(); |
traceの結果は、
AMF: a,b,c true
AMF: Tue Dec 8 18:52:43 GMT+0900 2009 true
AMF: root true
AMF: 10 false
AMF: true
JSONで試すと以下の結果でした。
//JSON------------------------------------------ file = File.desktopDirectory; file = file.resolvePath( "JSON.txt" ); stream = new FileStream(); stream.open( file, FileMode.UPDATE ); var json:String = JSON.encode( write ); stream.writeUTF( json ); stream.position = 0; read = stream.readUTF(); read = JSON.decode( json ); trace( "JSON:", read.arr, read.arr is Array ); trace( "JSON:", read.date, read.date is Date ); trace( "JSON:", read.xml, read.xml is XML ); trace( "JSON:", read.byte, read.byte is ByteArray ); file.clone(); |
traceの結果は、
JSON: a,b,c true
JSON: [object Object] false
JSON: [object Object] false
JSON: [object Object] false
ひとまず、型が記録されるAMFの方が読み込み後のチェックなど便利そうに思える。
もう一点、AMFはバイナリデータである為、保存したファイルをテキストエディタで開いてもなんのことかわからない。
JSONもなんのことかわからないといえば、わからない訳だけどフォーマットを手書きで記述することが出来ないこともないと思う。
この辺も用途によるかなぁと思った。
FlowerWallPaper
2009 年 12月 8 日 火曜日 kosukeBreakとReturn
2009 年 12月 7 日 月曜日 kosukeProgression 4 にある中断コマンド、BreakとReturnについて、その違いをメモ。
シーン /index/a/1 から シーン /index に移動する時、
シーン /index/a/1 のGotoで Breakの場合とReturnの場合で試します。
protected override function atSceneGoto():void{ this.addCommand( new Break(), // ←これを使う場合と new Return(), // ←これを使う場合の違い new Trace( "a/1 - atSceneGoto" ) ) } |
Breakを使った場合
new Trace( “a/1 – atSceneGoto” ) は実行されないが、その後はシーン遷移に基づき シーン /index まで実行される。
つまりBreakの場合、処理中のコマンドリストはBreak実行された時点で完了となり、その後のコマンドリストは引き続き処理される。
試しに、Gotoを以下にした場合、
protected override function atSceneGoto():void{ this.addCommand( new SerialList( { onComplete:function():void{ trace("onComplete"); } }, new Break(), new Trace( "SerialList" ) ), new Trace( "a/1 - atSceneGoto" ) ) } |
new Trace( “SerialList” )は処理されないけど、
trace(“onComplete”)
new Trace( “a/1 – atSceneGoto” )
は実行される。
Returnを使った場合
new Trace( “a/1 – atSceneGoto” ) は実行されず、Returnが実行された時点でシーン遷移が停止する。
Breakの時と同様以下を試すと
protected override function atSceneGoto():void{ this.addCommand( new SerialList( { onInterrupt:function():void{ trace("onInterrupt"); } }, new Return(), new Trace( "SerialList" ) ), new Trace( "a/1 - atSceneGoto" ) ) } |
new Trace( “SerialList” )
new Trace( “a/1 – atSceneGoto” )
とも処理されず、
trace(“onInterrupt”)は実行される。つまり中断になるわけです。
ちなみに、Progressionクラスの stop() メソッドもReturnと同じっぽい。
試しに以下を実行した場合も結果は同じだった。
protected override function atSceneGoto():void{ this.addCommand( new SerialList( { interruptType:CommandInterruptType.SKIP, onInterrupt:function():void{ trace("onInterrupt"); } }, function():void{ manager.stop(); }, new Trace( "SerialList" ) ), new Trace( "a/1 - atSceneGoto" ) ) } |
@nifty 年賀状 2010
2009 年 11月 20 日 金曜日 kosuke@nifty 年賀状 2010 用にオリジナル年賀状素材を7点作りました。
といっても作ったのは僕じゃありませんが。Yさんのお仕事。
@nifty 年賀状 2010には多数年賀状素材が揃ってます。
年賀状をお探しの際は是非ご覧ください。
@nifty 年賀状 2010 用 オリジナル年賀状素材
http://nenga.nifty.com/
Producer / 株式会社グッドファーム・プランニング
Design&Illustration / Yoco,Nakamura.nipx
Proxyクラスのこと
2009 年 11月 9 日 月曜日 kosukeProxyクラスはどんな時使うものか見当のつかない状態だったのだけど、便利に使えそうなケースが見つかったのでエントリー。
Proxyについてはここに説明がある。
ActionScript 3.0 言語およびコンポーネントリファレンス
これ読んでも正直なんのこっちゃって思っていた。
思いついた方法は、複数のオブジェクトのいくつかのプロパティを一斉に変更したい時、Proxyを介して変更するってもの。いろんな方法があると思いますが、Proxyを介して行うのもなかなか便利そうです。
以下のように複数の表示オブジェクトがあり、それらのプロパティを変更したいとします。
まず、表示オブジェクトを配列なりVectorなりに格納する。
var vector:Vector.<MovieClip> = new Vector.<MovieClip>(); vector.push( this.arrow0 ); vector.push( this.arrow1 ); vector.push( this.arrow2 ); vector.push( this.arrow3 ); |
これをProxyを継承したクラスに渡す。
this.proxy = new ProxyMcGroup( vector ); |
Proxyを継承したクラスではVectorを変数に代入する。
継承する時はdynamicで宣言しておいた方が、proxy.xのようにドットシンタックスでプロパティを参照してもエラーにならないのでいいと思う。
public dynamic class ProxyMcGroup extends Proxy{ private var _item:Vector.<MovieClip>; public function ProxyMcGroup( _item:Vector.<MovieClip> ){ super(); this._item = _item; } } |
Proxyを継承したクラスのsetPropertyをオーバーライトし、Vectorを繰り返し処理でプロパティを変更する。
override flash_proxy function setProperty(name:*, value:*):void{ for each( var i:MovieClip in this._item ){ i[name] = value; } } |
ひとまずこれだけで、複数オブジェクトのプロパティを一斉に変更できるようになる。
複数オブジェクトが同じプロパティを持つことを前提に、どのプロパティも変更できる(これが便利なポイント)はずです。
このままだと、setterだけなので取得が出来ない。
また例えばTweenerで動かす時はTweenerがhasOwnPropertyを使ってプロパティの有無を確認したりするので、必要に応じて拡張していく。
サンプルのは以下のようプロパティ値をDictionaryに保存するようにしたり、for…inステートメントでプロパティの参照が出来るようにしてみた。
package{ import __AS3__.vec.Vector; import flash.display.MovieClip; import flash.utils.Proxy; import flash.utils.flash_proxy; import flash.utils.Dictionary; public dynamic class ProxyMcGroup extends Proxy{ private var _item:Vector.<MovieClip>; private var _dictionary:Dictionary; private var _prop:Array; public function ProxyMcGroup( _item:Vector.<MovieClip> ){ super(); this._item = _item; this._dictionary = new Dictionary(true); this._prop = []; } override flash_proxy function callProperty(methodName:*, ...args):*{ trace( "callProperty:" + methodName + "," + args ); return; } override flash_proxy function getProperty(name:*):*{ trace( "getProperty:" + name ); return this._dictionary[name]; } override flash_proxy function setProperty(name:*, value:*):void{ trace( "setProperty:" + name + "," + value ); if( !this._dictionary.hasOwnProperty(name) ){ this._prop.push( name ); } this._dictionary[name] = value; for each( var i:MovieClip in this._item ){ i[name] = value; } } override flash_proxy function hasProperty(name:*):Boolean{ trace( "hasProperty:" + name ); return this._dictionary.hasOwnProperty(name); } override flash_proxy function deleteProperty(name:*):Boolean{ trace( "deleteProperty:" + name ) this._prop = this._prop.filter( function( item:*, index:int, array:Array ):Boolean{ if( item == name ) return false; else return true; } ); return delete _dictionary[name]; } override flash_proxy function getDescendants(name:*):*{ return; } override flash_proxy function nextNameIndex(index:int):int{ trace( "nextNameIndex:" + index ); if( index < this._prop.length){ return index + 1; } else{ return 0; } } override flash_proxy function nextName(index:int):String{ trace("nextName:" + index); return this._prop[index - 1]; } override flash_proxy function nextValue(index:int):*{ trace("nextValue:" + index); return this._dictionary[ this._prop[index - 1] ]; } } } |
動かしているところはProgressionのSerialListを使っています。
動かす前に使うプロパティに値を入れておく。
this.proxy.rotation = 0; this.proxy.alpha = 1; this.proxy.scaleX = 1; this.proxy.scaleY = 1; var comm:SerialList = new SerialList( { onComplete:function():void{ this.execute(); } }, new DoTweener( this.proxy, { rotation:360, transition:"easeInOutExpo", time:2 } ), new DoTweener( this.proxy, { alpha:0.5, transition:"easeNone", time:1 } ), new DoTweener( this.proxy, { scaleX:1, scaleY:1, _bezier:{ scaleX:0, scaleY:0 }, transition:"easeInOutExpo", time:1 } ), new DoTweener( this.proxy, { rotation:0, transition:"easeInOutExpo", time:2 } ), new DoTweener( this.proxy, { alpha:1, transition:"easeNone", time:1 } ) ); comm.execute(); |
ProxyはTweensy FXの時に久しく出てきて今回の使い方を気づいた。
思いもよらないところでつながることは良くある。何かを調べるってことは良いことだ。
インバースキネマティックを試す
2009 年 11月 6 日 金曜日 kosukeFlash CS4で目玉機能の一つでありながら、なんとなく影が薄いインバースキネマティック。
その理由か否か試してみてわかったことで、スクリプトからはちょっと使いづらいなぁと思った。
試してみたのはこちら。なんだか夢に出てきそうな気持ち悪さ。
rotation関連のスライダーは花の幹の部分の全ジョイントを変更。
IKMoverは花のアイコンを動かしています。
まず、インバースキネマティックの構造定義をアーマチュアといい、シンボルをつなぐ場合、表示オブジェクトが骨(ボーン)でつながった構造になります。ちなみにシェイプにボーンを通すことも出来る。
▲オーサリングツールで設定
アーマチェアはIKArmatureクラスで定義されるのだけど、これがスクリプトでは作れない。
Flashのオーサリングツールからしか作れないということ。
つまり、new IKArmature()とかして、スクリプトから表示オブジェクトをボーンでつないでいくことは出来ません。定義されたアーマチェアの構造を変更するのも無理。たとえばスクリプトでボーンを追加するとかも出来ない。
じゃあ、スクリプトで何が出来るかっていうとオーサリングツールで設定できるプロパティの変更とボーンの接続部(ジョイント)を移動させるとかです。
たとえば今回使った回転関連のプロパティは以下のように対応しています。
蛇足ですが、上のキャプチャで最小の-45°のところ、スクリプトで取得するとラジアンになって返ってくる。でもスクリプトで設定するときは角度。うーん…。
普通の使用方法ならスクリプトですることも大抵はポーズの変更をすることかと思う。
そうなるとポーズを作るのに単純なアニメーションはスクリプトでもと思いますが、いわゆる機械的じゃないアニメーションというか…規則的でないアニメーションはタイムラインで作り込んでいく方が効率いいってなるんだよなぁ。何か工夫を考えたい。
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を変更出来るようにしています。挙動の違いが確認できると思う。
やっぱり便利になってるなぁ。