2008年11月アーカイブ

現在の Progression にはシーンオブジェクトにイベントフローが実装されていますが、 表示オブジェクトとは少し挙動が違っています。(Progression 3.0.7 現在)

useCapture や eventPhase, bubbles あたりの値が有効化されていませんので、これを実装してみました。

変更を加えたファイルを以下に置いておきます。
jp/progression/events/progression_event
jp/progression/events/SceneEvent
jp/progression/scenes/SceneObject

未だ as ファイルだと表示できなくて、拡張子を .txt に変えてますが。
リンク先を trac.progression.jp の方に変更しました。

まとめてダウンロードされる場合はこちら。
eventflow.zip

以下、解説を少し。
var sceneA:SceneObject = createScene("sceneA");
var sceneB:SceneObject = createScene("sceneB");
var sceneC:SceneObject = createScene("sceneC");

sceneA.addScene(sceneB);
trace("----");
sceneB.addScene(sceneC);
trace("----");
sceneA.removeScene(sceneB);

function createScene(name:String):SceneObject
{
    var scene:SceneObject = new SceneObject(name);
    scene.addEventListener(SceneEvent.SCENE_ADDED, eventHandler, false);
    scene.addEventListener(SceneEvent.SCENE_REMOVED, eventHandler, false);
    scene.addEventListener(SceneEvent.SCENE_ADDED, eventHandler, true);
    scene.addEventListener(SceneEvent.SCENE_REMOVED, eventHandler, true);
    return scene;
}

function eventHandler(event:Event):void 
{
    trace(event.target.name, event.currentTarget.name, event.type, event.eventPhase);
}

出力結果
sceneB sceneA sceneAdded 1
sceneB sceneB sceneAdded 2
sceneB sceneA sceneAdded 3
----
sceneC sceneA sceneAdded 1
sceneC sceneB sceneAdded 1
sceneC sceneC sceneAdded 2
sceneC sceneB sceneAdded 3
sceneC sceneA sceneAdded 3
----
sceneB sceneA sceneRemoved 1
sceneB sceneB sceneRemoved 2
sceneB sceneA sceneRemoved 3


変更したのは SceneEvent クラスと SceneObject クラスの 2 つ。

まず SceneEvent クラスに以下を追記。
override public function get target():Object 
{
    return super.target
        && progression_event::$target 
        || super.target;
}

override public function get currentTarget():Object 
{
    return super.currentTarget 
        && progression_event::$currentTarget
        || super.currentTarget;
}

override public function get eventPhase():uint 
{
    return progression_event::$eventPhase || super.eventPhase;
}

progression_event var $target:Object = null;
progression_event var $currentTarget:Object = null;
progression_event var $eventPhase:uint = 0;
ここで SceneObject から値に手を加えられるようにするために、 progression_event という namespace ファイルを jp/progression/events/ 内に追加しています。

package jp.progression.events 
{
    public namespace progression_event 
        = "http://www.progression.jp/events/progression_event";
}


dispatchEvent() にイベントオブジェクトを渡したときに target に値が設定されていると、 dispatchEvent 内で Event.clone() が呼び出されるので、 super.target が null のときは null を返すようにしています。
もし $target が設定されていて super.target も設定されている場合は $target の値を、 $target が null でも super.target が設定されている場合は super.target の値を返します。
これは、 dispatchEvent() の内部以外での clone() メソッドの呼び出しへの配慮でもあります。

次はちょっと長いですが、 SceneObject に追加した内容。
/**
 * イベントフローのための内部参照
 */
private var $parent:SceneObject;

/**
 * キャプチャ段階のイベントに登録されたリスナーを保持しておくための EventIntegrator オブジェクト
 * 
 * コンストラクタ内に以下を追記
 * $captureEvent = new EventIntegrator(this);
 */
private var $captureEvent:EventIntegrator;

override public function dispatchEvent(event:Event):Boolean 
{
    if (event is SceneEvent)
    {
        return $dispatchEvent(event as SceneEvent);
    }
    return super.dispatchEvent(event);
}

private function $dispatchEvent(event:SceneEvent):Boolean
{
    var bool:Boolean = true;
    
    if (event.eventPhase == EventPhase.AT_TARGET)
        event.progression_event::$target = this;
    
    event.progression_event::$currentTarget = this;
    
    if (event.bubbles 
        && $parent 
        && event.eventPhase <= EventPhase.AT_TARGET)
    {
        var phase1:SceneEvent = event.clone() as SceneEvent;
        phase1.progression_event::$eventPhase = EventPhase.CAPTURING_PHASE;
        phase1.progression_event::$target
            = event.progression_event::$target;
        bool = $parent.dispatchEvent(phase1);
    }
    
    if (event.eventPhase == EventPhase.CAPTURING_PHASE)
    {
        bool = $captureEvent.dispatchEvent(event);
    }
    else
    {
        bool = super.dispatchEvent(event);
    }
    
    if (event.bubbles 
        && $parent 
        && event.eventPhase >= EventPhase.AT_TARGET)
    {
        var phase3:SceneEvent = event.clone() as SceneEvent;
        phase3.progression_event::$eventPhase = EventPhase.BUBBLING_PHASE;
        phase3.progression_event::$target = event.target;
        bool = $parent.dispatchEvent(phase3);
    }
    return bool;
}

override public function addEventListener(
    type:String, listener:Function, useCapture:Boolean = false,
    priority:int = 0, useWeakReference:Boolean = false):void 
{
    if (useCapture)
    {
        $captureEvent.addEventListener(
            type, listener, false, priority, useWeakReference);
    }
    else
    {
        super.addEventListener(
            type, listener, false, priority, useWeakReference);
    }
}

override public function removeEventListener(
    type:String, listener:Function, useCapture:Boolean = false):void 
{
    if (useCapture)
    {
        $captureEvent.removeEventListener(type, listener, false);
    }
    else
    {
        super.removeEventListener(type, listener, false);
    }
}

override public function hasEventListener(type:String):Boolean 
{
    return super.hasEventListener(type)
        || $captureEvent.hasEventListener(type);
}

override public function willTrigger(type:String):Boolean 
{
    var bool:Boolean = hasEventListener(type);
    if ($parent) bool = bool || $parent.hasEventListener(type);
    return bool;
}

override public function removeAllListeners(
    completely:Boolean = false):void 
{
    super.removeAllListeners(completely);
    $captureEvent.removeAllListeners(completely);
}

override public function restoreRemovedListeners():void 
{
    super.restoreRemovedListeners();
    $captureEvent.restoreRemovedListeners();
}
ここでは EventDispatcher のメソッドをそれぞれオーバーライドして処理内容に手を加えています。
具体的には、 addEventListener() と removeEventListener() で useCapture の値で 登録するオブジェクトを分けています。これはイベント呼び出し時にイベントフェーズによる振り分けができないためです。

dispatchEvent() 内は、親シーンへ遡ってイベントを呼び出すように変更しています。

更に、 _registerScene() と _unregisterScene() にも少し修正を加えます。

_registerScene の変更箇所
// イベントフロー時の参照先を登録する
scene.$parent = scene._parent;

// イベントリスナーを登録する
//scene.addExclusivelyEventListener( SceneEvent.SCENE_ADDED, 
    dispatchEvent, false, int.MAX_VALUE, true );
//scene.addExclusivelyEventListener( SceneEvent.SCENE_REMOVED, 
    dispatchEvent, false, int.MAX_VALUE, true );
//addExclusivelyEventListener( SceneEvent.SCENE_ADDED_TO_ROOT, 
    scene.dispatchEvent, false, int.MAX_VALUE, true );
//addExclusivelyEventListener( SceneEvent.SCENE_REMOVED_FROM_ROOT, 
    scene.dispatchEvent, false, int.MAX_VALUE, true );

// イベントを送出する
scene.dispatchEvent( new SceneEvent(
    SceneEvent.SCENE_ADDED, true, false, scene ) );

// ルートシーンが存在し、変更されていれば
if ( root && previousRoot != root ) {
    // イベントを送出する
    scene.dispatchEvent( new SceneEvent(
        SceneEvent.SCENE_ADDED_TO_ROOT, true, false, scene ) );
}

_unregisterScene の変更箇所
// イベントを送出する
scene.dispatchEvent( new SceneEvent( SceneEvent.SCENE_REMOVED, true, false, scene ) );

// ルートシーンが存在せず、変更されていれば
if ( !scene._root && previousRoot != scene._root ) {
    // イベントを送出する
    scene.dispatchEvent( new SceneEvent( SceneEvent.SCENE_REMOVED_FROM_ROOT, true, false, scene ) );
}

// イベントフローのための参照を削除
scene.$parent = null;

// イベントリスナーを解除する
//scene.completelyRemoveEventListener( 
    SceneEvent.SCENE_ADDED, dispatchEvent );
//scene.completelyRemoveEventListener( 
    SceneEvent.SCENE_REMOVED, dispatchEvent );
//completelyRemoveEventListener( 
    SceneEvent.SCENE_ADDED_TO_ROOT, scene.dispatchEvent );
//completelyRemoveEventListener( 
    SceneEvent.SCENE_REMOVED_FROM_ROOT, scene.dispatchEvent );
追加・削除された子シーンにイベントリスナーの登録を行っていた処理をコメントアウトしています。
また、イベントフローのための親シーンの参照を登録・解除しています。

あとは、 dispatchEvent に渡されるイベントオブジェクトの bubbles の値を true に変更しました。
bubbles が false に設定された場合はイベントフローを発生させないようにしているためです。

以上で、表示オブジェクトのようなイベントフローの実装ができます。
ただ、 EventIntegrator での実装を、あまり理解せずに手を加えているので、どこかに不都合はあるかもしれないです。
すこし使い倒してみる必要はあるかと思います。

役に立つかもしれない情報から、全く無意味としか思えない情報まで。
ひとつひとつエントリーするほどのものでもないので、以下にまとめて書く。


■設定されたステージサイズを取得する

stage.addEventListener(Event.ACTIVATE, activateHandler);

function activateHandler(event:Event):void
{
    event.target.removeEventListener(Event.ACTIVATE, activateHandler);	
    
    var defaultWidth:int = root.loaderInfo.width;
    var defaultHeight:int = root.loaderInfo.height;
}
これらの値はステージサイズが変更されても変わらない。 ただし、コンテンツの読み込みが完了しないうちに loaderInfo.width 等を呼び出すとエラーが吐き出されるので、使用には注意。

■レイヤーマスク

タイムライン上で設定されたマスクには不審な点がいくつかある。

  • マスクオブジェクトはマスクを解除しても表示されなくなる。ただし getRect() や getBounds() 等の矩形情報は残ってる。表示だけがなくなる。
  • マスクオブジェクトは深度 (index) が、対象オブジェクトより下に変更される。
  • 対象オブジェクトのmask, scrollRect プロパティとは無関係
  • マスクを解除する場合は "mask = null" ではなく、マスクオブジェクトをremoveChild() でステージ上から削除するか、setChildIndex() 等を用いて深度を変更する。
  • 被マスクオブジェクトの mask プロパティにオブジェクトを指定すると、2重でマスクが掛けられる。
  • AS から、別のオブジェクトを被マスクオブジェクトに隣接する深度に設定すると、そのオブジェクトもマスクの対象となる。
    もし、現在の被マスクオブジェクトの深度が一番上になっている場合、同階層に addChild() で配置されたオブジェクトも、自動的にマスク対象となってしまう。
  • マスクオブジェクトの深度を変更すると、マスクは一度解除されるが、マスクオブジェクトの1つ上の深度にオブジェクトを配置すると、マスク対象になる。
  • マスクオブジェクトを他のオブジェクト内に移動させた場合、元のマスクは解除されるが、移動先でもマスク効果は有効であるため、移動後も上記のルールが適応される。例えば、
    B.addChild( A.getChildByName("maskObject") );
    とした場合、A でのマスク効果は解除されるが、B 内では有効となる。
結局のところスクリプトからオブジェクトがレイヤーマスクか、または被マスクオブジェクトであるかを調べる方法は不明。
ActionScript とは掛け離れた部分で制御されているのかもしれない。

■scrollRect を指定したオブジェクトのサイズを調べる

function getDefaultRect(target:DisplayObject):Rectangle
{
    if(target.parent)
    {
        var path:DisplayObjectContainer = target.parent;
        var index:uint = path.getChildIndex(target);
        path.removeChild(target);
    }
    
    var bounds:Rectangle = target.transform.pixelBounds;
    bounds = new Rectangle(
            bounds.x * 0.2 - target.x, 
            bounds.y * 0.2 - target.y, 
            bounds.width * 0.2, 
            bounds.height * 0.2);
    
    if(path)
    {
        path.addChildAt(target, index);
    }
    
    return bounds;
}
transform.pixelBounds を使うと元の大きさが取得できるが、
そのままだと、親オブジェクトの変形の影響をもろに受けてしまうので、
一度 removeChild() で引き剥がした方がいい場合もある。時と場合による。

また、対象が stage 上に配置されていない場合、なぜか取得した値がすべて 5 倍になっているので、その場合は割ってやる必要がある。
さらに、AS 側で [Embed] タグを用いて埋め込んだ場合も、ステージに配置したにもかかわらず、次のフレームに移るまでは、同じく値が 5 倍になっており、 これを判定するのは面倒なので、サイズを調べる場合は、とにかく一度 removeChild() するのが無難かもしれない。

■クラスは Class クラスのインスタンスオブジェクト

trace(new Sprite() is Sprite); // true
trace(Sprite is Class);        // true
trace(Sprite is Sprite);       // false
trace(Class is Class);         // true
定義されたクラスのコンストラクタ関数と constructor は別物。
object.constructor は Class から生成されたインスタンスへの参照であって関数ではない。
trace(new Object().constructor == Object); // true
trace(Object is Class);                    // true
trace(Object is Function);                 // false
コンストラクタ関数を参照する方法は、真っ当な方法では、おそらく以下の方法しかないかと。
class ExampleClass
{
    public var constructorMethod:Function;
    
    public function ExampleClass()
    {
        constructorMethod = arguments.callee;
    }
}
ただしコンストラクタ関数からインスタンスを生成することはできない。
コンストラクタ関数が MethodClosure インスタンスであるため。
MethodClosure については次項。

■MethodClosure extends Function

Function クラスは final class なので、継承はできない。
が、定義されたクラスメソッドのコンストラクタは Function を継承した MethodClosure クラス。
ちなみに関数が MethodClosure か判別する方法は以下。
function isMethodClosure(func:Function):Boolean
{
    return func is 
        Object(arguments.callee).constructor;
}

var test1:Function = function():void {}
function test2():void {}

trace( isMethodClosure(test1) );     // false
trace( isMethodClosure(test2) );     // true

■クラス内での prototype の使用

package
{
public class PrototypeClass
{
    public function PrototypeClass()
    {
        prototype.property = "property of prototype";
    }
    
    prototype.property = null;
    
    prototype.methodProperty = function():void
    {
        trace("call methodProperty");
    }
}
}
var instance:PrototypeClass = new PrototypeClass();
Object(instance).methodProperty(); // call methodProperty
trace(instance["property"]);       // property of prototype
使い道が見当たらない。

■マウスイベントをまとめて無効にする

stage.mouseChildren = false;
先日Progressionセミナー後の懇親会で教えてもらった。

■Vector の型指定

var vector:Vector.<Object> = new Vector.<Object>();
trace(getQualifiedClassName(vector));
// output : __AS3__.vec::Vector.<Object>
var vector:Vector.<*> = new Vector.<*>();
trace(getQualifiedClassName(vector));
// output : __AS3__.vec::Vector.<*>
var vector:Vector.<*> = new Vector.<Object>();
trace(getQualifiedClassName(vector));
// output : __AS3__.vec::Vector.<Object>
var vector:Vector.<*> = new Vector.<String>();
trace(getQualifiedClassName(vector));
// output : __AS3__.vec::Vector.<String>
var vector:Vector.<*> = new Vector.<int>();
// TypeError: Error #1034: 強制型変換に失敗しました。
   Vector.<int>@1683e41 を __AS3__.vec.Vector.<*> に変換できません。
上記のように、型の指定には変数等に使用するときと同様に、 * (アスタリスク)を使うことができるが、 なぜか Number, int, uint の数値型を指定した Vector オブジェクトを代入しようとすると失敗する。

trace(new Vector.<Object>() is Vector);          // false
trace(new Vector.<Object>() is Vector.<Object>); // true
trace(new Vector.<Sprite>() is Vector.<Object>); // false
trace(new Vector.<Sprite>() is Vector.<*>);      // true
trace(new Vector.<String>() is Vector.<*>);      // true
trace(new Vector.<int>() is Vector.<*>);         // false
こういうもんなんだろうか。

上記に反して、以下はキャストしてから引数に入れないとエラーが発生する。
以下のコードはエラーが出る例。
var vector:Vector.<Object> = new Vector.<Object>();
test(vector);

function test(vector:Vector.<*>):void {}

var sprites:Vector.<Sprite> = Vector.<Sprite>([new Sprite()]);
var display1:Vector.<DisplayObject> = Vector.<DisplayObject>(sprites);
var display2:Vector.<DisplayObject> = sprites;
上記は display1 ではキャスト成功するが、display2 のようにそのまま代入しようとするとエラーが出る。

■Vector は Array とは違う

Vector は Array と似ているが、Array とは全く別物なので、以下のようなことは出来ない。
function test(...args:Vector.<*>):void {}
Error: ...残りパラメータ定義のキーワードの後に指定したパラメータで使用できるのは、Array データ型のみです。

func.apply(null, Vector.<int>([1,2,3]));
TypeError: Error #1116: Function.prototype.apply の 2 番目の引数は配列でなければなりません。

var matrix:Vector.<Number> = 
    Vector.<Number>(String("10000010000010000010").split(""));
new ColorMatrixFilter(matrix);
Error: 型 __AS3__.vec:Vector.<Number> の値が、関連しない型 Array に暗黙で型変換されています。


■Vectorオブジェクトの動的生成

動的に Vector オブジェクトを作成したい場合、下記のように変数からではエラーが出て失敗する。
var theClass:Class = Sprite;
var vector:Vector.<theClass> = new Vector.<theClass>();
一度文字列にして作成するとうまくいく。
var T:Class = Sprite;
var classPath:String = getQualifiedClassName(T).replace("::", ".");
var theClass:Class = getDefinitionByName("Vector.<" + classPath + ">") as Class;
trace( getQualifiedClassName(new theClass()) );
// output : __AS3__.vec::Vector.<flash.display::Sprite>