「AS3.0」カテゴリーアーカイブ

AIRでの一時ディレクトリの使用

放置状態だった「FLASHで作る AIRアプリケーション レシピブック」を再開。この中にファイルをデスクトップ等にドロップして保存するサンプルがあるのだけれど(RECIPE 003)、これを自分で書いてみたら、うまく動かなかった。
具体的には初回のみ成功して、2回目以降はNG(Finder側に移動不可マークが出る)。ムービープレビューをやり直しても、Flashを再起動してもダメで、OSを再起動しないとうまくいかない(それでもOS再起動後1回のみで、あとはNG)。
具体的なファイル保存の方法は・・・
1.一時ディレクトリを作成(File.createTempDirectory())
2.一時ディレクトリに保存ファイルを作成(File.resolvePath())
3.FileStreamで保存ファイルにBitmapDataを書き込み(FileStream.writeBytes())
とファイルを作っておいて、ファイルをドラッグ開始したときに
1.クリップボードに保存ファイルをセット(Clipboard.setData())
2.ドラッグ処理を設定(NativeDragManager.doDrag())
して、ドラッグ完了時に・・・
1.クリップボードの保存ファイルが、ドロップした場所に移動する
(NativeDragActions.MOVE/自動で行われる)
2.一時ディレクトリを削除する(File.deleteDirectory(true))
という手順になっています。
いろいろ調べてみると、どうも一時ディレクトリで引っかかっているようで、一時ディレクトリの削除をはずすと問題なく動作する。削除自体はエラーにならないけど、ちゃんと解放されていないとか、そんな感じなのかな?
ただ一時ファイルは削除しないと新規に作られ続けるので、明示的にディレクトリを作って、それを削除する方法に変更した。具体的には・・・
1.File.desktopDirectoryとFile.resolvePath()を使って、明示的なディレクトリを作成
(File.createDirectory())
2. 明示的なディレクトリを削除する(File.deleteDirectory(true))
という感じに変更しました。
テスト環境はMacOSX10.5.8+Flash CS4です。サンプルファイルのAIRでも同様でした。但し知人にWindows Vista+Flash CS3で試してもらったところ、問題なく動作したらしい。OSXの問題なのか、AIR1.5なのか、CS4なのか…。特定できませんでしたが、とりあえず対処できたので良しとします。
書籍のサンプルファイルはサポートページからダウンロードできるので、気になる方は試してみて下さい。もし何かわかったら教えて下さい。

Shift-JISのXMLを読み込む

普段XMLを使う場合は、UTF-8で作成するので意識したことなかったけど、今回使用するRSSがたまたまShift-JISでそのままでは2バイト文字が文字化け。System.useCodePage を使ってもダメで、どうしたものかと検索したら見つかりました。
XMLのURLLoaderのデータフォーマットをURLLoaderDataFormat.BINARY として、バイナリでロードした後にByteArray.readMultiByteでShift-JIS >> UTF-8 への変換をすればOKでした。厳密には、EUC-JPでは別に対処が必要とかあるみたいですけど、今回はShift-JISが変換できればいいので(笑)。下記のサイトを参考にしました。感謝。

XMLの名前空間を指定解除する場合の注意点

AS3.0では名前空間を持つXMLは、名前空間を使わないとアクセスできない。で、名前空間を指定した後クリアしていたんだけど、そのクリア方法に問題在り。nullでなくundefinedを使うこと。
nullだと次にXMLを使ったときに、namespaceの初期値が xmlns=”null” になってしまう。わからなくてさんざん悩んだ。ざっくりだけど、該当部分だけ貼っておきます。一番最後が問題だった箇所です。

//名前空間を抽出・保存(接頭辞で指定)
for (var i:int = 0; i<doc_xml.namespaceDeclarations().length; i++) {
switch (doc_xml.namespaceDeclarations()[i].prefix) {
case “” :
var ns_def:Namespace = new Namespace(doc_xml.namespace(doc_xml.namespaceDeclarations()[i].prefix));
break;
case “dc” :
var dc:Namespace = new Namespace(doc_xml.namespace(doc_xml.namespaceDeclarations()[i].prefix));
break;
case “rdf” :
var rdf:Namespace = new Namespace(doc_xml.namespace(doc_xml.namespaceDeclarations()[i].prefix));
break;
}
}

//名前空間のデフォルト値を設定

default xml namespace = ns_def;

//(XMLの処理)

//名前空間のデフォルト値を設定解除(ここはundefinedで。nullは不可)
default xml namespace = undefined;

テキストノードがない場合の戻り値

xmlで引っかかった点。

テキストノードを表示する処理を書いていて、字数制限が必要でString処理を挟んだら動かなくなった。テキストノードに値がないので、空白かな〜と思っても判定できず。調べてみたら、テキストノードに値がない場合の戻り値はundefinedだった。
こんな感じでテスト。

var test_xml:XML =
<itemlist>
<msg>おはよう</msg>
<msg></msg>
<msg>こんにちは</msg>
<msg />
<msg>おやすみ</msg>
</itemlist>;

for each(var item:XML in test_xml.msg) {
trace(“text=”+item.text(),(item.text() == “”),(item.text() == null),(item.text() == undefined))
}

——出力結果

text=おはよう false false false
text= false false true
text=こんにちは false false false
text= false false true
text=おやすみ false false false

BitmapData.draw()とクロスドメイン

画像をロードする処理で、同じ画像を何度も読み込むのはムダなのでビットマップをコピーして使うことにしたら、BitmapData.draw()で止まってしまった。いろいろ調べてみたら・・・

・別ドメインの画像を読み込むだけなら、ポリシーファイルがなくても問題なし。
・別ドメインの画像を読み込んでデータの操作をする場合は、ポリシーファイルが必要。

ということらしい。
BitmapData.draw()のヘルプには、次のように書いてる。

読み込んだビットマップイメージを source として使用する場合も制限があります。読み込んだイメージが呼び出し元と同じドメインからのものの場合、draw() メソッドの呼び出しは成功します。また、イメージのサーバーのクロスドメインポリシーファイルで、draw() メソッドを呼び出す SWF コンテンツのドメインに許可を付与できます。この場合、LoaderContext オブジェクトの checkPolicyFile プロパティを設定し、このオブジェクトを context パラメータとして、イメージの読み込みに使用する Loader オブジェクトの load() メソッドを呼び出すときに使用する必要があります。これらの制限は、アプリケーションセキュリティサンドボックス内の AIR コンテンツには適用されません。

う〜ん、知らなかった。毎回のload()で設定するのは面倒なので、Security.loadPolicyFile()でポリシーファイルを指定することで解決。
下記のサイトにお世話になりました。

動的生成したオブジェクトはインスタンス名で参照できない

例えば、次のようにMovieClipを動的に階層化する。

var target_mc:MovieClip = new MovieClip();
this.addChild(target_mc)

var img_mc_on:MovieClip = new MovieClip();
var img_mc_off:MovieClip = new MovieClip();

target_mc.addChild(img_mc_on);
target_mc.addChild(img_mc_off);

これを

trace(“target_mc.img_mc_off”,target_mc.img_mc_off,target_mc.img_mc_off.name)

とすると、下記のようにエラーになる。

TypeError: Error #1010: 条件は未定義であり、プロパティがありません。

「ああ、インスタンス名をつけておかないといけないのか」と、下記のように変更しても同じエラーが出る。

var target_mc:MovieClip = new MovieClip();
this.addChild(target_mc)

var img_mc_on:MovieClip = new MovieClip();
var img_mc_off:MovieClip = new MovieClip();

img_mc_on.name = “img_mc_on”;
img_mc_off.name = “img_mc_off”;

target_mc.addChild(img_mc_on);
target_mc.addChild(img_mc_off);

trace(“target_mc.img_mc_off”,target_mc.img_mc_off,target_mc.img_mc_off.name)

「何で???」と悩んでいたら、下記のサイトを発見。
要は「動的に生成したインスタンスは、インスタンス名で参照できないので、addChildしたオブジェクトのプロパティにインスタンス本体を入れておく」必要があるらしい。で、次のように改訂。

var target_mc:MovieClip = new MovieClip();
this.addChild(target_mc)

var img_mc_on:MovieClip = new MovieClip();
var img_mc_off:MovieClip = new MovieClip();

img_mc_on.name = “img_mc_on”;
img_mc_off.name = “img_mc_off”;

target_mc.addChild(img_mc_on);
target_mc.addChild(img_mc_off);

//これが重要!!!
//動的に生成したインスタンスは、インスタンス名で参照できないので
//addChildしたオブジェクトのプロパティにインスタンス本体を入れておく。
target_mc[“img_mc_on”] = img_mc_on;
target_mc[“img_mc_off”] = img_mc_off;

trace(“target_mc.img_mc_off”,target_mc.img_mc_off,target_mc.img_mc_off.name)

これで下記のように正しく参照されてることが確認できます。

target_mc.img_mc_off [object MovieClip] img_mc_off

ちなみにnameを設定しない場合には、下記のようになります。

target_mc.img_mc_off [object MovieClip] instance3

オーサリングでステージ上に作成したオブジェクトは、AS2.0までと同じく使えるのでわかりにくいですね。
上記F-siteの記事によれば『前述「オーサリング時に配置したMovieClipインスタンス』では、タイムラインに予め配置して[プロパティ]インスペクタで設定したインスタンス名は、そのまま変数のように参照として扱うことができました。これは、ActionScript 3.0では、Flashがインスタンス名と同じ名前の変数を自動的に宣言して、そこにインスタンスの参照を格納する仕組みになっているからです」ということらしい。う〜ん。こういうことは実際に遭遇してみないと気づかないですね。
でも最初に作ったtarget_mcが問題なく参照されているのは何でだろう?推察するに、ステージ(タイムライン)上に生成したオブジェクトは、オーサリング時に配置したオブジェクトと同様に自動的に処理されるってことなんでしょうね。
<追記>2010.8.18
最後の「でも最初に作ったtarget_mcが問題なく参照されているのは何でだろう?」については、野中さんから「参照には変数に設定されている必要がある」というご指摘いただきました。詳しくはコメント欄を参照のこと。

swf全体でのサウンド制御

AS3.0ではサウンド制御も変更されていて、基本的にSoundクラスとSoundChannelクラスを使う。例えば、外部mp3を再生する場合は。こんな感じ。

var sd_song:Sound;
var sd_channel:SoundChannel;

function xInitSound_song():void {
sd_song = new Sound();
var url:String = “mp3/” + “Pissism-Blade.mp3”;
var urlReq:URLRequest = new URLRequest(url);

sd_song.addEventListener(Event.COMPLETE,onCompleteSound_load);
sd_song.load(urlReq);
}

function onCompleteSound_load(evt:Event):void {
trace(“—ロード完了”,evt.target);
}

function xStartSound_song(evt:MouseEvent):void {
sd_channel = sd_song.play();
}

xInitSound_song();
btn_play.addEventListener(MouseEvent.CLICK,xStartSound_song);

で、使用しているチャンネルの音量は、SoundChannel.soundTransform.volumeで制御することになるのだけれど、

function xVolume0_song(evt:MouseEvent):void {
var st:SoundTransform = sd_channel.soundTransform
st.volume = 0
sd_channel.soundTransform = st
}

と書いても、サウンド再生中は制御できる(volume=0になる)けど、停止しているときに0にしても再び再生するとvolumeは0にはならない。要はSoundChannelのsoundTransformは再生しているSoundクラスに対してのみ有効ということらしい。SoundChannelという器の設定ではないんですね。
コレだと全体の音量制御ができない。いろいろ検索してみたら、SoundMixer.soundTransformで制御すると、swf全体の制御が出来るらしい。

function xVolume0_song(evt:MouseEvent):void {
var st:SoundTransform = SoundMixer.soundTransform;
st.volume = 0;
SoundMixer.soundTransform = st;
}

SoundMixerクラスを使うことで、swf全体のサウンド制御ができ、かつサウンドの再生・停止に依存せずに音量制御ができる。
検索中に下記サイトでこの辺の制御をクラス化したものを発見。コレ、便利そう。

AS3.0でWebServiceを使う

AS3.0にはFlash Remotingも見つからないし、必要に迫られて探してみたら見つかりました。Flex SDKのライブラリとSWCにパスを通して、FlashCS4でFlex Webservice componentを使えるようにしてやればいいようです。これでmx.*クラスが使えます。関連リンクを備忘録として記します。


上記2つ目と3つ目は同じビデオですが、3つ目には動作可能なサンプルflaファイルがあります。目的のWebServiceでも動作確認済みです。[OSX10.5.8+FlashCS4]

    Timer()で発生する誤差

    Timerクラスを使ってのインターバル処理で音を鳴らしていたら、間隔を縮めていくとタイミングがずれてきた。ググってみると、やっぱり起こるらしい。以下、関連リンク。


    ていたら、間隔ミリ秒単位の(なるべく)正確な制御は、やっぱりgetTimer()とEvevt.ENTER_FRAMEの方がいいのかな?

    Arrayの落とし穴(1) – Array.sort()

    表示リストのindexをArrayで管理していて、はまった罠。
    var list:Array = [7,8,9,10]
    list.sort()
    trace(list)
    —10,7,8,9
    var list:Array = [7,8,9,10]
    list.sort(Array.NUMERIC)
    trace(list)
    —7,8,9,10
    Array.sort()ってデフォルトだと文字扱いでソートになるのね。いつもインスタンス名とかでsortするときは、必ず「mc00」「mc01」とか文字列(主にインスタンス名など)に使ってて、あまり数値そのもののソートをしたことないってことか…。お恥ずかしい話でした(^^;。
    ※一応、カテゴリをAS3.0にしているけど、AS2.0でも同じです。
    [追記]2011.5.10
    Arrayについて整理するため、カテゴリを追加してタイトルも変更しました。