サイトトップ

Director Flash 書籍 業務内容 プロフィール

Macromedia Flash非公式テクニカルノート

Tweenのアニメーションが途中で止まる

ID: FN0711001 Product: Flash Platform: All Version: CS3/ActionScript 3.0

問題
Tweenインスタンスを大量に作成して、長めの時間をかけてトゥイーンさせると、アニメーションは途中で止まってしまう場合があります。

再現の可能性は100%ではないものの、以下のフレームアクション(スクリプト001)では高い確率で一部のTweenアニメーションが停止します(再現できなかったときは、forループの回数を500とか1000に上げ、フレームレートも120fpsにすると、発生しやすくなります)。

スクリプト001■100個のSpriteインスタンスに10秒のTweenアニメーションを設定する

// フレームアクション
import fl.transitions.Tween;
import fl.transitions.TweenEvent;
import fl.transitions.easing.None;
var n:int = 0;
for (var i:int = 0; i<100; i++) {
  var mySprite:Sprite = xCreateCircle(i);
  var myTween:Tween = new Tween(mySprite, "alpha", None.easeNone, 1, 0, 10, true);
  myTween.addEventListener(TweenEvent.MOTION_FINISH, onFinish);
}
function onFinish(eventObject:TweenEvent):void {
  var myTween:Tween = Tween(eventObject.target);
  trace(myTween.obj.name, ++n);
}
// テスト用のSprite生成関数
// ブルーの丸をステージのランダムな位置に配置
function xCreateCircle(i:int):Sprite {
  var mySprite:Sprite = new Sprite();
  mySprite.name = "sprite"+i;
  mySprite.graphics.beginFill(0x0000FF);
  mySprite.graphics.drawCircle(0, 0, 5);
  addChild(mySprite);
  mySprite.x = stage.stageWidth*Math.random();
  mySprite.y = stage.stageHeight*Math.random();
  return mySprite;
}

上記スクリプト001をフレームアクションに設定して[ムービープレビュー]を確認すると、青い丸のSpriteインスタンスが100個ランダムな位置に生成されます。SpriteインスタンスはTweenアニメーションにより、10秒間かけてアルファ0の透明になります。

正しくTweenアニメーションが終了すると、Tween.motionFinishイベントが呼出され、[出力]パネルにSpriteインスタンス名(sprite0〜sprite99)とその連番が表示されます。しかし、この問題が発生すると、全インスタンスの数だけ[出力]されず、残りのTweenアニメーションは途中で止まります(図001)。

図001■Tween.motionFinishイベントが一部のインスタンスにしか発生せずTweenは途中停止
図001


原因
Tweenクラスが内部的にDisplayObject.enterFrameイベントにリスナー関数を登録する際、「弱い参照」が指定されています(スクリプト002)[*1]。したがって、Tweenインスタンスに対する参照が他に存在しないと、ガベージコレクト(削除)されてしまうことが原因のようです。

スクリプト002■Tweenクラス

public class Tween extends EventDispatcher
{
  /**
  * @private
  */

  protected static var _mc:MovieClip = new MovieClip();
  // ...[中略]...
  private var _fps:Number = NaN;
  // ...[中略]...
  protected function startEnterFrame():void
  {
    if (isNaN(this._fps))
    {
      // original frame rate dependent way
      _mc.addEventListener(Event.ENTER_FRAME, this.onEnterFrame, false, 0, true);
    }
    // ...[後略]...


[*1]「弱い参照」については、オンラインヘルプ[ActionScript 3.0のプログラミング] > [イベントの処理] > [イベントリスナー]の「イベントリスナーの管理」の項、およびakihiro kamijo「イベントリスナ(AS3)とガーベジコレクション」をご参照ください。


対処法
Tweenアニメーションが終了するまでは、Tweenインスタンスを保持しておくべきでしょう。

前掲スクリプト001に修正を加え、Tweenインスタンスをタイムライン変数の配列に保持することにしてみました(スクリプト003)。アニメーションが終了し、Tween.motionFinishイベントが呼出されたときに、インスタンスを配列から削除します。

スクリプト003■Tweenインスタンスを配列に格納してアニメーション終了時に削除する

// フレームアクション
import fl.transitions.Tween;
import fl.transitions.TweenEvent;
import fl.transitions.easing.None;
var n:int = 0;
var tween_array:Array = new Array();
for (var i:int = 0; i<100; i++) {
  var mySprite:Sprite = xCreateCircle(i);
  var myTween:Tween = new Tween(mySprite, "alpha", None.easeNone, 1, 0, 10, true);
  tween_array.push(myTween);   // 配列にTweenインスタンスを保持
  myTween.addEventListener(TweenEvent.MOTION_FINISH, onFinish);
}
function onFinish(eventObject:TweenEvent):void {
  var myTween:Tween = Tween(eventObject.target);
  var bResult:Boolean = xDispose(myTween);   // 配列からTweenを削除
  trace(myTween.obj.name, ++n, bResult, tween_array.length);
}
// テスト用のSprite生成関数
// ブルーの丸をステージのランダムな位置に配置
function xCreateCircle(i:int):Sprite {
  var mySprite:Sprite = new Sprite();
  mySprite.name = "sprite"+i;
  mySprite.graphics.beginFill(0x0000FF);
  mySprite.graphics.drawCircle(0, 0, 5);
  addChild(mySprite);
  mySprite.x = stage.stageWidth*Math.random();
  mySprite.y = stage.stageHeight*Math.random();
  return mySprite;
}
// 配列からTweenインスタンスを削除する関数
function xDispose(obj:Tween):Boolean {
  var nLength:int = tween_array.length;
  for (var i:int = 0; i<nLength; i++) {
    if (tween_array[i] == obj) {
      tween_array.splice(i, 1);
      return true;
    }
  }
  return false;
}


作成者: 野中文雄
作成日: 2007年11月30日


Copyright © 2001-2007 Fumio Nonaka.  All rights reserved.