« 2006年06月 | メイン | 2006年08月 »

2006年07月26日

Arrayの追加メソッド

Flashはヘルプが充実していて下手な参考書よりも役に立つことがしばしばあるのですが、バージョンアップ時に追加されたメソッドがこっそり書かれているため、チェックは欠かせません。
ActionScript3.0でも既存のビルトインクラスにメソッドが幾つか追加されていますが、特にArrayにはたくさん追加されています。
コールバックメソッドを指定するものがほとんどなので、まとめて覚えてしまうのがよさげです。


Array.indexOf
Array.lastIndexOf
これは今まではprototypeで強引に追加定義したりしたものですが。やっとこさ実装されました。
引数で指定した要素が配列のどこにあるか探すメソッドです。

Array.forEach
配列の全ての要素について特定の処理を施したい時に使います。
for each 文とほとんど変わりませんが、同じ処理が何度も使いまわされるような場合に向いています。

Array.every
これは配列の全ての要素について、ある条件を満たしているかどうかをチェックするために使います。
条件自体は引数で指定するコールバックメソッド内に記述します。

Array.some
これはArray.everyと似ているのですが、ある条件を満たす要素が配列内に存在するかどうかをチェックするために使います。
こちらも条件自体は引数で指定するコールバックメソッド内に記述します。

Array.map
これはPerlでよく使いますね。
配列の全ての要素に一定の処理を施した値を要素とする新しい配列が欲しい時に使います。
コールバックメソッドの返り値が、新しい配列の要素となります。

Array.filter
こちらもPerlで頻出のgrep関数と同じで、要素のフィルタリングを行います。
コールバックメソッド内に記述する条件を満たす要素のみから構成される新たな配列を返します。


処理速度的には for each で回すのがもちろん一番速いのですが、処理が高速化されているActionScript3.0では些細なレベルっぽいです。
ただでさえイベントだらけのコールバックメソッドだらけになるので、この際コールバックメソッドで固めてしまうもよし、やらなくても済むところは徹底的に省くもよし、開発者のソース管理能力とセンスが問われるところでしょうか。

2006年07月20日

ビットマップエフェクター

随分前から作ってあったのだけれど、ActionScript3.0の勉強にかまけて放置気味になっていたので公開します。
なお、結構な負荷がかかります。

BITMAP EFFECTOR

最初の制作動機が、映像入力にフィルタを多重にかけるサンプルをFlashIDE上で作るのが激しく面倒くさいから簡単に実験できるツールが欲しい、といった感じだったのでウェブカメラ推奨です。
ActionScript3.0ではビットマップデータを画像データ化することが可能になっているので、最終的には nexImage みたいなものを目指したいなと・・・
ものすごく時間かかりそうなので不可能に近いのですが、夢はでっかく。

【操作方法】
画面下部に各種エフェクトの設定ウィンドウを開くためのボタンが浮遊しています。
ドラッグで掴んで回して飛ばせます。
なお、こんなところに無駄な仕込みをすると重くなるだけという悪例なので真似しないようにしましょう。

まず右端のBitmapDataウィンドウはビットマップ保管庫です。
NEWでアイテムを増やし、LOADで外部画像を読み込むことができます。
なお、7個以上アイテムを増やすとウィンドウを飛び出しますが、今のところ対応する気力がないので後回し決定です。

次にeffectウィンドウについて、とその前に。
各種エフェクトの設定ウィンドウには、APPLYとREGISTERというボタンがあります。
APPLYボタンは設定したエフェクトをビットマップに反映させるためのものですが、REGISTERボタンはエフェクトの設定そのものを登録します。
登録されたエフェクトはそのままでは機能しませんが、effectウィンドウ上部のREALTIME:OFFをクリックするとREALTIME:ONとなり、登録されたエフェクトが順にリアルタイムにかけられます。
もちろん登録しすぎると負荷かかりまくリングなので注意をば。
なお、大元の入力が随時変化するウェブカメラ映像であることが前提なので、ウェブカメラを持ってない人には全く意味のない機能です。

ウェブカメラ映像のCAMウィンドウ、BitmapDataウィンドウの各アイテム画像、各種エフェクト設定ウィンドウの入力・出力画像の部分はそれぞれドラッグ&ドロップにより内容を転写することができます。
まずはじめはCAMウィンドウ内部をドラッグし、BitmapDataウィンドウの001画像にドロップしましょう。
なお、CAMウィンドウに対しては当然ながらドロップできません。


登録エフェクト一覧を投稿できるようにすると面白いなと考えていますが、グラデーション生成など他にもあったらいいな機能がありすぎて立ち往生気味です。
なお、今後のバージョンアップについては未定です。

2006年07月18日

イベントフロー図解

前回のエントリで ActionScript3.0 におけるイベントフローを軽く説明してみたのだけれど、文字だけでは限界があると思ったので flash でサンプルを作ってみました。
なお、これ自体は ActionScript3.0 で組んだものではありません。







これはインスタンスの親子関係をビジュアル化したもので、mc3 の親が mc2、mc2 の親が mc1 となります。
マウスポインタの指し示す最前面のインスタンスがマウスイベントのターゲットとなるため、この図解ではクリックしたインスタンスがターゲットとなります。
イベントはキャプチャフェーズ、ターゲットフェーズ、バブリングフェーズの順に伝わりますが、キャプチャフェーズはイベントリスナ登録時に明示的に設定しない限りはイベントを受け取りません。

各種インスタンスはイベントに応じてメソッドを呼びますが、引数として渡されるイベントの target プロパティの参照先は共通で上記のターゲットとなります。

これを理解した上で覚悟しなければならないことがあります。
それはボタンに対するマウスイベントの発生過程において、ボタンを内包する全てのインスタンスにイベントが通知されるということです。
サンプルを見ての通り、mc2 と mc3 のどちらをクリックしても mc1 のメソッドが呼ばれますが、イベントの主体は target プロパティを確認しないと分かりません。
このような全ての階層に渡ってイベントリスナ登録をすること自体稀かもしれませんが、かなり気を使わないと思わぬところで全く無関係のメソッドが呼ばれる可能性があるということを意識しておく必要があると思います。

2006年07月16日

ボタン作成のためのイベントフロー制御

従来のボタンの挙動をActionScript3.0で再現するためには、リスナー登録まわりでひと工夫する必要があります。
fladdict.net blog の【AS3メモ MouseEventについて】で言及されているように、マウスイベントはそのインスタンス上でしか発生しないため releaseOutside を実現するためには stage にリスナー登録する必要が出てきます。
また releasereleaseOutside の判別にロールオーバー中かどうかのフラグで対応できるのと同様、rollOverdragOver の判別や rollOutdragOut の判別についてもマウス押下中かどうかのフラグで対応できます。

ところでマウスイベントがインスタンス上でしか発生しないという事実は、言い換えるとマウスカーソル下の全てのインスタンスに対して必ずマウスイベントが発生してしまうマウスカーソル下の最前面に配置されているインスタンスとそれを包括する全ての親インスタンスに対して一部を除く全てのマウスイベントが発生してしまうということです。
ボタンの releaseOutside を実現するためにボタン領域外で MouseEvent.MOUSE_UP を発生させると、そこがたまたま別のボタン上であった場合にそのボタンの release も同時に処理されてしまうことになります。
もっと単純に、ボタンをクリックするとそのボタンが配置されている Sprite をはじめ、ボタンのパスに含まれる全インスタンスにイベントが発生します。

もちろんこれは望ましくないのですが、簡単に制御することができます。

アドビ上条さんのブログエントリ【イベントフロー】に書かれているように、これらのイベントはキャプチャフェーズ→ターゲットフェーズ→バブリングフェーズの順で伝わります。

・・・正直な話、これだけでは意味が激しくわからないので、先ほどのボタンの話を例に挙げてみることにします。
ボタンのパスを仮に以下のように設定してみます(便宜上AS1.0/2.0表記)

_root.mc1.mc2.btn

また _root、mc1、mc2、btn の全てにマウスイベント MOUSE_DOWN を登録し、イベントが発生すると[インスタンス名:mousedown]とデバッグ出力するようにしておきます。

さて、ここで btn をクリックするとデバッグ出力には次のように表示されます

btn:mousedown
mc2:mousedown
mc1:mousedown
_root:mousedown

クリックしたインスタンスから階層を上へと辿りながらイベントが発生していることがわかります。
これはイベントフローのバブリングフェーズにあたりますが、addEventListener はこれがデフォルト設定となっています。

仮にこれを逆順、つまり _root から階層を下へと辿りながらイベントを発生させたいのであれば、addEventListener の第3引数を true にする必要がありますが、これはキャプチャフェーズでイベントを受け取るかどうかのフラグにあたります。

話を本題に戻しますが、btn をクリックした時にそのボタン以外にはイベントを受け取って欲しくないようにするためには、イベントフローを途中でカットします。
以下のスクリプトは btn 上でマウスが押下された時に呼ばれるイベントハンドラです。

private function onPressHandler( e:MouseEvent ):void {
 trace("btn:down");
 e.stopPropagation();
}

ここで stopPropagation() というメソッドが呼ばれていますが、これはイベントフローを中断するためのメソッドです。
これが呼ばれると、以降のインスタンスにはイベントは伝わらなくなりイベントハンドラが呼ばれなくなります。


また、releaseOutside を実現するにあたって他のボタンの release が発生しないようにしなければならないので、ボタンが押された時に


stage.addEventListener( MouseEvent.MOUSE_UP, onReleaseOutsideHandler );

と記述して、onReleaseOutisdeHandler で stopPropagation() を呼べばよいと考えてしまいがちですが、実はこれでは上手くいきません。
なぜなら、イベントフローのバブリングフェーズにおいて stage には最後にイベントが伝わるからです。
ここで思い出すのは一見使い道のなさそうなキャプチャフェーズです。
キャプチャフェーズではバブリングフェーズとは逆に stage から順にイベントが伝わるので、ここでイベントフローの中断をかけると上手くいきそうです。

そこで上記のスクリプトを以下のように書き換えます。


stage.addEventListener( MouseEvent.MOUSE_UP, onReleaseOutsideHandler, true );

これでどのイベントハンドラよりも先にこの onReleaseOutisdeHandler が呼ばれるので、stopPropagation() により余計なイベントを抑えることができます。


おおよそ全てのボタンについてこの処理を埋めておく必要があるので、どう考えてもカスタムクラスを作るべきではないでしょうか。
というか作ってるんですが、Takaさんも作ってるわけで上手くすり合わせできればなーと。

2006年07月15日

マウス入力とキーボード入力

ActionScript3.0ではキーボード入力の検出に枷が嵌められています。
それは検出を行うインスタンスがフォーカス状態でなければいけないということです。

キー押下を検出するには、適当なインスタンス(ドキュメントルートでも可)にイベントリスナを登録してさらにフォーカスさせておく必要があります。

stage.focus = this;
addEventListener( KeyboardEvent.KEY_DOWN, onKeyDownHandler );


ここまではまだいいのですが実はこのフォーカス、ボタン押下時に奪われます。
たとえばシンボルの移動にキーボードを割り当てて、あるキーを押している間だけ動き続けるようにします。
これは Keyboard.KEY_DOWN と Keyboard.KEY_UP のそれぞれについてリスナー登録すれば実現できます。

このとき画質変更ボタンを画面に配置していたりなんかすると、ボタンを押した瞬間フォーカスがそのボタンに移るのでキーボード入力が検出できなくなります。
さらにひどいことに、キー押下だけならまだしもキー押上まで拾えなくなるため、キー押下中にボタンを押してしまうとシンボルの移動が止まらないなんてことに!!

これを解決するにはフォーカス移動を強制的にキャンセルすることです。
ボタン btn が押下された瞬間に、フォーカスをキーボード入力検出用インスタンスに戻すためには

public コンストラクタ() {
 stage.focus = this;
 addEventListener( KeyboardEvent.KEY_DOWN, onKeyDownHandler );
 addEventListener( KeyboardEvent.KEY_UP, onKeyUpHandler );
 btn.addEventListener( MouseEvent.MOUSE_DOWN, onMouseDownHandler );
}

private function onKeyDownHandler( e:KeyboardEvent ):void {
 // 移動開始処理
}
private function onKeyUpHandler( e:KeyboardEvent ):void {
 // 移動終了処理
}
private function onMouseDownHandler ( e:MouseEvent ):void {
 stage.focus = this;
}

といった具合に、ボタンに対して MouseEvent.MOUSE_DOWN のイベントリスナを登録して、メソッドでフォーカスを戻す処理を記述しておきます。
これで一応はキーボード入力が奪われずに済みます。

が、これが本当に正しいやり方なのかは試行錯誤中なのでよく分からず・・・
もっと賢いやり方があれば誰か教えてください。

2006年07月13日

強制バージョンアップ

サーバを介してリアルタイム対戦ゲームをFLASHで構築する際になるべく組み込んでおくべき機能、それは強制バージョンアップです。
これは重大な不具合を発見した場合やゲームバランスを大幅に変更したくなった場合に、ログイン中のユーザが極端に有利(不利)になるのを避けるために必要なのですが、ただサーバ側から再起動しただけでは修正前のファイルのキャッシュをそのまま読み込む可能性があります。

これらを解決するために、Flashとサーバプログラムの双方にバージョン番号をハードコーディングしておき、ログイン時に比較することで古いバージョンのファイルでログインしているユーザを遮断できます。
もちろんただ遮断するわけではなく、新しいバージョンがリリースされていることを通知できるように特別なエラーコードを返すようにします。
こうすることで全ユーザが同じバージョンのファイルでログインできるようになるわけです。

THE DAY OF SAGITTARIUSについては、管理ツールから再起動可能な10人対戦版には仕込んであります。


ソケット通信型なら強制ログアウトさせることができるのですが、単純なCGI通信型だと定期的にバージョンチェック用のクエリを投げない限りは難しくなります。
サーバ負荷も考えなくてはならず、バージョンの違いにより致命的な問題が発生しない限りは厳密にチェックしないor全くチェックしないのが妥当でしょうか。

2006年07月04日

長門さん、実は仲間を裏切っていた!?

急に路線変更するわけじゃなく、THE DAY OF SAGITTARIUS 0.3を開発してる時に気になったので言及しておきたかったこと。
アニメ・涼宮ハルヒの憂鬱「射手座の日」で、長門さんがハッキング実行してコンピ研の索敵モードがオンになったシーンに注目。

ハッキング前 - SOS団の艦隊が見えない
hack_before.jpg

ハッキング後 - SOS団の艦隊と索敵範囲が見える
hack_after.jpg


ハッキング前にはコンピ研の艦隊しか見えていないのに対して、ハッキング後は長門さんの分艦隊も含めたSOS団の艦隊に加えて索敵範囲も丸見えになっています。
索敵モードをオンにされた結果、索敵艇を全く飛ばさずに余裕ぶっこいていたコンピ研はいきなり可視範囲が大きく削り取られて慌てふためいたと原作にもあるのですが、このシーンを見る限りでは全くの逆。
可視範囲が増えてめちゃくちゃ有利になってます。
妄想してみると、コンピ研は正々堂々とプレイしていたのに長門さんの手引きによって索敵モードがオフになって、SOS団が圧倒的に不利になりましたとさ。

仲間を裏切るなんてひどいよ長門さん・・・もちろん作画ミスなんだろうけど。

2006年07月02日

どうみても不便です

Flash9インスコしてActionScript3.0でゴリゴリ書いてみたんですが、Flex2と比べると開発環境があまりにも不便すぎて涙が出そうになりました。
Flashで描いたシンボルを使えるってことぐらいじゃないのか。

これまでと違って下手なスクリプトを書いてると実行時エラーが頻発するので、書いたそばからチェックしてくれるIDEの方が圧倒的に便利なんですよね。
こりゃやっぱりFlex2.0購入するしか!
それでも実行時エラーを出してしまうわけだけど。

ドツボにはまりかけたのが、removeChild でインスタンスを削除する時にイベントハンドラもちゃんと消しておかないといけないということ。
Event.ENTER_FRAME を消し忘れてたので、削除後のnull代入が連発したw
こういった後始末に対する意識とかが既存のActionScript職人の弱いところなんだろうなと痛感しました。
ということで、ActionScript3.0の勉強日記なんてのもありかもしれない。