マスクしているオブジェクトを変えるとマスクされているオブジェクトがおかしくなる不思議


再現方法

  1. タイムラインのレイヤー1にマスクしたいオブジェクトを配置する(maskedObjとする)
  2. タイムラインのレイヤー2にマスクするオブジェクトを配置する(maskObj1とする)
  3. レイヤー2をレイヤー1のマスクに設定する
  4. レイヤー2の適当なフレームに空のキーフレームをうち、別のマスクするオブジェクトを配置する(maskObj2とする)
  5. レイヤー1をレイヤー2のフレーム数まで伸ばす

この状態で、maskedObjに対して、maskObj1にマスクされている時に操作した情報は、マスクがmaskObj2に変わった瞬間に消えるみたいです。普通はremoveしないと消えないようなaddChildしたオブジェクトや、addEventListenerしたイベントがまるっと消えてしまいます。

マスクしているオブジェクトが変わっただけなのに、マスクされているオブジェクトが初期化されてしまうのです。明らかに変です。

FlashCS4でもFlashCS5でもなります。理由は謎です。ちなみにActionScriptから操作した場合は問題ないようです。Flashのタイムラインからマスクをかけた場合は、複数オブジェクトに対してマスクがかけられたりもするので、動作そのものが違うためでしょうか。

「マスクはASで操作する」あるいは「後で付加するあるいは後で付け直す」とすればこのバグを防げるみたいです。

テスト用fla+asファイル

有名なバグだったりするんでしょうか?こんなので何時間も取られてはたまらないですよね。・・・それは金曜日の私ですw

基準点を中心にするJSFLを考える1

普段はアニメーションはTweenerで作っていて、久しぶりにタイムラインでバリバリやる感じのものを作ろうと思い、久しぶりにクラシックトゥイーンなどしてみたんですが、基準点を操作するのがなかなかメンドクサイ。そこでJSFLで何とかならないかと思ったわけです。

作ったのは以下のようなものです。複数選択には対応していません。

var doc = fl.getDocumentDOM();
doc.enterEditMode('inPlace');
var beforeRect = doc.getSelectionRect();
doc.align('vertical center', true);
doc.align('horizontal center', true);

//サイズが奇数値だとセンタリングしたときに端数がでるので調整
var tempRect = doc.getSelectionRect();
var roundX = Math.round(tempRect.left) - tempRect.left;
var roundY = Math.round(tempRect.top) - tempRect.top;
roundX = Math.round(roundX * 10) / 10;
roundY = Math.round(roundY * 10) / 10;

doc.moveSelectionBy({x:roundX, y:roundY});


var afterRect = doc.getSelectionRect();

var moveX = Math.round(beforeRect.left - afterRect.left);
var moveY = Math.round(beforeRect.top - afterRect.top);

doc.exitEditMode();
doc.moveSelectionBy({x:moveX, y:moveY});
doc.setTransformationPoint({x:0, y:0});

とりあえずはシェイプ1つ置いたものならば、基準点と変形点を真ん中に変更できました。

作ってみて気になったのは、getSelectionRectで取得できるRectangle.leftやRectangle.topの値が、100.499など、中途半端な値になってしまうこと。

置き方が悪いのかといろいろやってみましたが、上手くいかないようなので、四捨五入しました。

次はこれをもうちょっと使い易くしてみようと思います。

カスタムクラスのインスタンスをtraceした時に配列のように内容を出力したい

久々に。仕事がてんてこまいだったのがようやく落ち着いてきました。

表題の件、ずっとできるはず、と思いながらうまく行かなかったのですが、ある時ふと思い立って調べ直してみたらうまく行ったのでメモ。

SpriteやMovieClipを拡張して作るときは、toString()をoverrideして、戻り値を書き出したい値にすればできる様子。

ただ、Objectを拡張しただけのクラス(何もextendsしてないクラス)だとこれだとできない。

Error: Method marked override must override another method.

みたいに怒られます。

どうしたらいいものかと思ってたんですが、直に

public class Hoge {
  public var name:String;
  public var text:String;
  public function toString():String {
    return '['+[name, text]+']';
  }
}

と書けばちゃんとtraceした時にtoStringとして動作するようです。いろいろ調べてみたらtoStringはObjectのObject.prototype.toStringとして書かれているというような記述がありました。

参考:http://help.adobe.com/ja_JP/ActionScript/3.0_ProgrammingAS3/WS5b3ccc516d4fbf351e63e3d118a9b90204-7f3f.html

真似してみたらこれでもできた。

public class Hoge {
  public var name:String;
  public var text:String;
  prototype.toString = function():String {
    return '['+[this.name, this.text]+']';
  }
}

//----------
var hoge:Hoge = new Hoge();
hoge.name = 'nameTest';
hoge.text = 'textTest';
trace(hoge);  //[nameTest,textTest]

prototypeオブジェクトについてもうちょっと調べてみようと思います。

パブリッシュする時に一緒にtrace文を省略した上で最適化保存してくれるJSFL

あったら便利かなと言うことで作ってみました。

var doc = fl.getDocumentDOM();
var profileXML = doc.exportPublishProfileString();
var newProfile = profileXML.replace("<OmitTraceActions>0</OmitTraceActions>", "<OmitTraceActions>1</OmitTraceActions>");
doc.importPublishProfileString(newProfile);
doc.publish();
doc.importPublishProfileString(profileXML);
doc.saveAndCompact();

こちらショートカットに登録すると便利に使えます。・・・使えるんですが、ショートカットを登録してもFlashを起動しただけだとショートカットが有効にならず、必ず1回キーボードショートカットのウィンドウを開かないとできない・・・。何故・・・。

下は開いているflaファイル全てに対して実行できるようにしたもの。

if (fl.documents.length > 0){
     for (i=fl.documents.length-1; i>=0; i--){
          cleanPublish(fl.documents[i]);
     }
}
else{
     fl.trace("ドキュメントが開かれていません。");
}

function cleanPublish(doc){
     fl.setActiveWindow(doc);
     var profileXML = doc.exportPublishProfileString();
     var newProfile = profileXML.replace("<OmitTraceActions>0</OmitTraceActions>", "<OmitTraceActions>1</OmitTraceActions>");
     doc.importPublishProfileString(newProfile);
     doc.publish();
     doc.importPublishProfileString(profileXML);
     doc.saveAndCompact();
}

最初はただ単にfl.documents[i]を関数に渡しただけでできるものかと思ったんですが、こちらと同じ罠に引っかかりました。

http://level0.kayac.com/2009/08/flatrace.php

対処法はいくつか試したんですが、

fl.setActiveWindow(doc);

でOKじゃないかと思います。これで現在開いているドキュメントをアクティブにできるので、fl.documents[i]が選択された状態になり、ドキュメントのプロパティも新しいものが選択されるはずです。

失敗したときは書き出し先が上書きされるという恐ろしい事態になったので、作るときは注意しないとですね。

ちなみに上のスクリプトのご利用は自己判断でご自由にどうぞ。

パッケージ名を自動で全部書き換えてくれるJSFLがあったら便利そう。ただ、JSFLでやる意味はあるのか、という疑問もありますね。ライブラリ内のものは除いて。

flFile.runCommandLine()

を使うとコマンドラインに命令を出せるようなので、こちらからテキスト編集プログラムを実行させた方が早いかもしれないですね。

getChildByNameの謎

通常、タイムラインで配置したオブジェクトや、外部から読み込んだswfに名前をつけようとしても

Error: Error #2078: タイムラインに配置されたオブジェクトの name プロパティは修正できません。

というエラーが出て名前を変更できない。

それならば、と思ってカスタムクラスを作って、

private var _name:String;
public override function get name():String{return _name;}
public override function set name(value:String):void{_name = value;}

ってやって無理矢理名前をセットできるようにしてみましたが、これをやると、

//hogeを名前をつけられるようにしたオブジェクトとする
hoge.name = 'test';//エラーは出ない
OBJ.addChild(hoge);

trace(OBJ.getChildByName('test'));//結果はnull
trace(OBJ.getChildAt(0).name)//結果はtest

という謎の状態になってしまいました。

カスタムクラスのsetterで

super.name = value;

とするとやはりエラーが出るので、setterのnameは上のような動作ではなく、違う動作をすると思われます。何か内部的なIDを割り振っているんでしょうか。あるいは子の名前のリストを内部的に持っているとか?

別の方法で何とかしましたが、思いつきだけで動かしていると痛い目に遭いますね・・・。

余談:

既にunloadしているMovieClipをもう1度unloadしようとすると、エラーになる。

自分でunloadを仕込んだのに忘れてunloadしようとしたら

Error #2099: 読み込み中のオブジェクトは、十分に読み込まれていないので、その情報を表示することができません。

というエラーが。loaderが取得できないのかと思ったら、そもそもその前に削除していたという・・・。まあ確かにloaderが取得できない、という想像は正しいですがw

BitmapとShapeの扱い

ビットマップはタイムラインに置いておくと、パブリッシュ時にShapeに変換される(ただし、AS書き出ししていると、Bitmapになる)。というような話を前に書いてましたが、もうちょっと調べた話。

タイムラインに置いたShapeは隣接レイヤーにShapeがあると統合される。これは上記ビットマップのデータにも有効であり、AS書き出しをしていなければShapeに統合される。

したがって、レイヤー3つに

画像/画像/Shape

という感じでデータをタイムラインに置き、そのクリップ内で

trace(this.numChildren);

すると結果は

1

となる。

このとき、真ん中の画像をASに書き出しすると、

3

となる。

trace(this.getChildAt(0));
trace(this.getChildAt(1));
trace(this.getChildAt(2));

の結果は

[object Shape]
[object Bitmap]
[object Shape]

になる。

勝手にShapeが合成されない方法を編み出したらきっと便利かもしれない。今のところ一番手っ取り早そうなのはシンボルに変換することだろうか。スクリプトで書けばまったく問題にならない部分ではありますが。