UDN
Search public documentation:

ScaleformActionScriptBestPracticesJP
English Translation
中国翻译
한국어

Interested in the Unreal Engine?
Visit the Unreal Technology site.

Looking for jobs and company info?
Check out the Epic games site.

Questions about support via UDN?
Contact the UDN Staff

UE3 ホーム > ユーザーインターフェイスと HUD > 「Scaleform GFx」 > 「Scaleform GFx」 ActionScript のベストプラクティス

「Scaleform GFx」 ActionScript のベストプラクティス


概要


ActionScript は、ネイティブ マシンコードにコンパイルされません。バイトコードに変換されます。このバイトコードは、解釈される言語よりも速いが、コンパイルされたネイティブコードよりは低速です。ActionScript は低速になる場合がありますが、たいていのマルチメディア表現においては、グラフィックやオーディオ、ビデオといったアセット (コードではなく) が、しばしばパフォーマンスを縛る要因となっています。

最適化のためのテクニックの多くは、ActionScript に固有のものではありません。最適化コンパイラをともなわないどのような言語でコードを書く場合にも使用されるよく知られたテクニックです。たとえば、各ループ イタレーションで変化しないアイテムをループ内から削除してループの外に置くと、ループが高速化します。

ALERT! 注意 : ActionScript は以降 AS と短縮します。
ALERT! 注意 : Virtual Machine (バーチャルマシン) は以降 VM と短縮します。

ActionScript のための一般的なガイドライン


以下の最適化によって、AS の実行速度が向上します。

  • SWF ファイルを Flash バージョン 8 以降にパブリッシュします。
  • AS の使用量を減らせば減らすほど、ファイルパフォーマンスが向上します。所定のタスクを実行するために書かれるコードの量を常に最小限にとどめます。AS は主にインタラクティビティのために使用すべきであって、グラフィカルなエレメントを作成するために用いるべきではありません。コードで attachMovie が多用されている場合は、FLA ファイルの構成を再考する必要があります。
  • スクリプトによるアニメーションをなるべく少なく使用するようにします。タイムラインによるアニメーションのほうが、通常はパフォーマンスで勝ります。
  • 文字列操作を多用しないようにします。
  • if 文を使用するループのムービークリップを過度に使用しないことによって、ターミネーション (プログラムの終了) を回避します。
  • on() および onClipEvent() イベントハンドラの使用を避けます。代わりに、onEnterFrame、onPress などを使用します。
  • フレーム上に 主要 AS ロジックに配置することを最小限にとどめます。代わりに、重要なコードを大量に関数内に配置します。 Flash AS のコンパイラは、旧式のイベントハンドラ (onClipEvent など) または フレーム内部に直接置かれたソースよりも、関数本体内部に置かれたソースの場合のほうが、著しく高速なコードを生成することができます。ただし、フレーム内にシンプルなロジックをとどめておくことは容認されます。たとえば、タイムライン制御 (gotoAndPlay、play、stop など) や他の重要なロジックです。
  • 複数のアニメーションをもつ長いタイムラインとともに、ムービークリップ内で「長距離」に及ぶ gotoAndPlay/gotoAndStop を使用することを避けます。
    1. 順送りの gotoAndPlay/gotoAndStop の場合は、現在のフレームからターゲットのフレームが離れているほど、gotoAndPlay/gotoAndStop のタイムライン制御にかかるコストが高くなります。したがって、最もコストが高い順送りの gotoAndPlay/gotoAndStop は、最初のフレームから最後のフレームに進む場合です。
    2. 逆送りの gotoAndPlay/gotoAndStop の場合は、タイムラインの開始地点からターゲットのフレームが離れているほど、タイムライン制御にかかるコストが高くなります。したがって、最もコストが高い逆送りの gotoAndPlay/gotoAndStop は、最後のフレームから、最後から 1 つ手前のフレームに進む場合です。
  • 短いタイムラインでムービークリップを使用します。gotoAndPlay/gotoAndStop のコストは、キーフレームの数とタイムラインアニメーションの複雑度に大きく依存します。したがって、gotoAndPlay/gotoAndStop を呼び出すことによって制御する予定の場合は、長く複雑なタイムラインを作成しないようにします。その代わりに、ムービークリップのタイムラインを、独立した複数のムービークリップに分割し、そのタイムラインを短くするとともに、gotoAndPlay/gotoAndStop の呼び出しを減らします。
  • 多数のオブジェクトを同時に更新する場合は、これらオブジェクトをグループとして更新することができるシステムを開発することが不可欠です。C++ 側で、GFxMovie::SetVariableArray の呼び出しを使用することによって、大量のデータを C++ から AS に渡します。この呼び出しの後に、アップロードされている配列に基づいて複数のオブジェクトを同時に更新させる呼び出しを続けることができます。複数の呼び出しを 1 つの呼び出しにグループ化することによって、通常、個別のオブジェクトを呼び出す場合の速度の数倍速くなります。
  • 1 つのフレームに大量の作業を実行させないようにします。さもなければ、GFx にステージをレンダリングする時間が足りなくなり、ユーザーが速度の低下に気がつく可能性が生じます。実行する作業を小さなかたまりに分割することによって、速度の低下が感知されることなく、所定のフレームレートで GFx がステージをリフレッシュできるようにします。
  • Object 型を過度に使用しないようにします。
    1. データ型のアノテーションは、詳細なものにすべきです。そうすることによって、コンパイラによる型チェックでバグを見つけることができるようになります。Object 型は、他によい代替物がない場合にのみ使用すべきです。
  • eval() および配列アクセス演算子を使用しないようにします。ローカルの参照を一度セットすることが、好ましく効率的であることが多いのです。
  • myArr.length 自体を使用するのではなく、条件として使用するループの前に Array.length を変数にアサインします。例 :

以下のコードを使用します。

var fontArr:Array = TextField.getFontList();
var arrayLen:Number = fontArr.length;
for (var i:Number = 0; i < arrayLen; i++) {
  trace(fontArr[i]);
}

以下のコードは使用しません。

var fontArr:Array = TextField.getFontList();
for (var i:Number = 0; i < fontArr.length; i++) {
  trace(fontArr[i]);
}

event を合理的かつ厳密に管理します。条件を使用して、呼び出す前にリスナーが存在している (null ではない) か否かをチェックすることによって、event リスナー配列をコンパクトに保ちます。

  • オブジェクトへの参照を解放する前に removeListener() を呼び出すことによって、明示的にリスナーをオブジェクトから削除します。
  • パッケージ名においてレベルの数を減らすことによって、起動時間を削減します。起動時に、AS の VM は、オブジェクトのチェーンを作成する必要があります (レベル 1 つにつき 1 つのオブジェクト)。さらに、各レベルのオブジェクトが作成される前に、AS のコンパイラは、if 条件文を加えることによって、レベルがすでに作成されているか否かをチェックします。したがって、com.xxx.yyy.aaa.bbb というパッケージがある場合は、VM が com、xxx、yyy、aaa、bbb というオブジェクトを作成するとともに、オブジェクトがそれぞれ作成される前に、オブジェクトの存在を確認する if オペコードが実行されることになります。objects/classes/functions のように深くネストされたアクセスも低速となります。理由は、名前を解決するために、各レベルをパースする必要があるためです (com を解決し、さらに xxx を解決し、さらに xxx 内部の yyy を解決する)。このような余分なオーバーヘッドを回避するために、パスを、単一レベルの一意な識別子 (c58923409876.functionName() など) にするためのプリプロセッサ ソフトウェアを使用してから SWF をコンパイルするデベロッパーもいます。
  • 同一の AS クラスを使用する複数の SWF ファイルからアプリケーションが構成されている場合は、これらのクラスを、コンパイル中における SWF ファイルの選択から除外します。これによって、実行時メモリの要件を削減することができます。
  • タイムライン内のキーフレーム上にある AS をコンパイルするのに時間がかなりかかる場合は、コードを分割して、複数のキーフレームにまたがって実行することを検討してみます。
  • 最終の SWF ファイルをパブリッシュする際に、trace() 文をコードから削除します。そのためには、[Publish Settings] (パブリッシュの設定) ダイアログボックス内の [Flash] タブ上で、[Omit Trace Actions] (トレースアクションの除外) チェックボックスを選択して、trace() 文をコメントアウトまたは削除します。これによって、実行時のデバッグのために使用されたあらゆる trace() 文を効率的に無効にすることができます。
  • 継承は、メソッドの呼びだす回数を増やし、メモリの使用量も増加させます。つまり、必要な機能をすべて含むクラスは、親クラスから機能のいくつかを継承するクラスよりも、実行時の効率が上がります。したがって、クラスの拡張性とコードの効率性の間で、設計上のトレードオフを行わなければならない場合があります。
  • ある SWF ファイルが、カスタムの AS クラス (例 : foo.bar.CustomClass) を含む別の SWF ファイルをロードし、さらに、その SWF ファイルをアンロードするならば、クラス定義がメモリに残ってしまいます。メモリを節約するには、アンロードされる SWF ファイルに含まれるカスタムのクラスをすべて明示的に削除します。次の例のように、delete 文を使い、省略されていない完全なクラス名を指定します。delete foo.bar.CustomCLass
  • すべてのコードを毎フレーム走らせる必要はありません。100% タイムクリティカルではないアイテムについては、(毎フレーム、コードの一部が交代する) フリップフロップを使用します。
  • onEnterFrames はできるだけ使用しないようにします。
  • 演算関数を使用せずに、データテーブルを事前計算します。
    1. 大量の演算を実行する場合は、値を事前計算し、その結果を変数の (擬似) 配列に格納することを検討します。データテーブルからこれらの値を取り出すほうが、GFx にオンザフライで演算させるよりもかなり高速です。

ループ

  • ループおよびあらゆる繰り返し動作に焦点を当てます。
  • 使用されるループの数とループに含まれるコードの量を制限します。
  • フレームベースのループは、必要が無くなり次第停止します。
  • ループ内から関数を複数回呼び出さないようにします。
  • ループ内に小規模な関数の内容を含めるほうが好ましいです。

関数

  • 可能な場合は必ず、深くネストする関数を避けるようにします。
  • 関数内で with 文を使用しないようにします。この演算子は、最適化を無効にします。

変数 / プロパティ

  • 存在しない変数、オブジェクト、関数を参照しないようにします。
  • 可能な場合は必ず、var キーワードを使用します。var キーワードを関数内部で使用することは特に重要です。その理由は、ActionScript のコンパイラがローカル変数へのアクセスを最適化する際に、変数をハッシュテーブルに入れて名前でアクセスするのではなく、インデックスによる直接的なアクセスをともなって内部レジスタを使用するためです。
  • ローカル変数で十分である場合に、クラス変数およびグローバル変数を使用しないようにします。
  • グローバル変数の使用を制限します。理由は、それらを定義するムービークリップが削除された場合、ガーベジコレクトされなくなるためです。
  • 必要がなくなったら、変数を削除するか、null にセットします。こうすることによって、ガーベジコレクションのためのデータがマークされます。変数を削除することによって、実行時のメモリ使用を最適化することができます。これは、不必要なアセットが SWF ファイルから削除されるためです。変数は、null にセットするよりも、削除したほうが好ましいです。
  • プロパティは、AS のゲッターおよびセッターメソッドを使用せずに、直接アクセスするようにします。ゲッターおよびセッターは、他のメソッド呼び出しよりもオーバーヘッドが大きくなります。

一般的な最適化のためのヒント


Flash のタイムライン

タイムラインのフレームとレイヤーは、Flash オーサリング環境の中で重要な 2 つの要素です。これらの領域は、アセットが配置される場所を示すとともに、ドキュメントの機能の仕方を決定します。タイムラインとライブラリの設定様態と使用様態によって、FLA ファイル全体とそのユーザビリティおよびパフォーマンス全般が影響を受けます。

  • フレームベースのループはなるべく使用しないようにします。フレームベースのアニメーションは、FPS と結びついていないタイムベースのモデルとは異なり、アプリケーションのフレームレートに依存します。
  • 必要がなくなり次第、フレームベースのループを停止します。
  • 複雑なコードのブロックは、可能な場合、 2 つ以上のフレームに分散させます。
  • 数百行に及ぶコードをともなうスクリプトは、数百に及ぶフレームベースのトゥイーンと同じ負荷がプロセッサにかかります。
  • コンテンツを評価することによって、アニメーション / インタラクションをそのタイムラインで簡単に達成できるのか、あるいは、AS を使用して単純化しモジュール式にできるか判断します。
  • デフォルトのレイヤー名 (例 : Layer 1、Layer 2) の使用を避けます。理由は、複雑なファイルを扱う場合、アセットを記憶したり配置する際に混乱するためです。

パフォーマンスに関する一般的な最適化

  • パフォーマンスを向上させるために、変換を統合する方法がいくつかあります。たとえば、3 つの変換をネストする代わりに、1 つのマトリックスを手動で計算します。
  • 一定時間速度が低下した場合は、メモリリークがないか調べます。必要がなくなったものは必ず破棄します。
  • オーサリング時に、多数の trace() 文を使用しないようにします。あるいは、テキストフィールドを動的に更新しないようにします。これらは、パフォーマンスの負担となります。更新はできるだけ少なくします。(すなわち、常に行うのではなく変更があった時にのみ行うようにします)。
  • 妥当な場合は、AS を含むレイヤーおよびフレームラベルのためのレイヤーを、タイムラインのレイヤースタックの一番上に配置します。たとえば、AS アクションを含むレイヤーを指定することが、適切で一般的なやり方となります。
  • フレームアクションを異なるレイヤーに置かないようにします。すべてのアクションを単一のレイヤーに集中させます。これによって、AS の管理が簡略化されるとともに、複数の AS 実行パスによって引き起こされるオーバーヘッドが除去されるため、パフォーマンスが改善されます。

advance (進行)

advance を実行するのに時間がかかりすぎる場合は、7 つの最適化が考えられます。

  • 毎フレームで AS のコードを実行しないようにします。onEnterFrame ハンドラも避けます。毎フレームコードを呼び出すためです。
  • イベント駆動型のプログラミング技法を使用し、変化が生じた場合にのみ、明示的な Invoke (呼び出し) 通知を通じて、テキストフィールドと UI ステートを変更します。
  • インビジブルなムービークリップ内でアニメーションを停止します。 (_visible プロパティを true にセットする必要があります。stop() 関数を使用してアニメーションを停止します)。これによって、そのようなムービークリップが Advance リストから除外されることになります。なお、親のムービークリップが停止している場合であっても、階層内にあるあらゆるムービークリップを一つ残らず (子も含めて) 停止する必要があります。
  • _global.noInvisibleAdvance 拡張機能の使用が必要となる代替的なテクニックもあります。この拡張機能は、インビジブルなムービークリップのグループを、それぞれを停止させることなく、Advance リストから除外するのに役立つ場合があります。この拡張機能のプロパティが true にセットされている場合は、インビジブルなムービークリップが Advance リストに追加されないため (子を含めて)、パフォーマンスが改善されます。なお、このテクニックが完全に Flash に対応しているわけではないということを覚えておいてください。Flash ファイルが、非表示のムービー内部における、いかなるタイプのフレーム単位の処理にも依存しないようにする必要があります。GFx 拡張機能を有効にすることを忘れないでください。そのためには、_global.gfxExtensions を true にセットすることによって、この (および他の) 拡張機能を使用できるようにします。
  • ステージ上のムービークリップの数を減らします。ムービークリップの不必要なネストを抑えます。ネストされたムービークリップそれぞれによって、advance 時に小さなオーバーヘッドが生じます。
  • タイムラインアニメーションおよび、キーフレーム、シェイプ トゥイーンを減らします。
  • GFx 2.2 およびそれ以前のバージョンでは、ビジブルでスクリーン上にありながら、MovieClip.noAdvance プロパティを true にセットすることによって advance または onEnterFrame を要求しない静的なムービークリップの advance を防ぐことが可能です。GFx 拡張機能を有効にする必要があります。そのためには、_global.gfxExtensions を true にセットすることによって、この拡張機能を使用できるようにします。なお、このことは、たいていのケースにおいて、GFx 3.0 およびそれ以降のバージョンでは必要となりません。GFx 3.0 は、停止したムービークリップを advance または処理することがありません。Advance におけるレンダリング グラフ全体を処理しなくなったためです。その代わりに、アクティブなリストを保持するようになりました。この改善によってnoAdvance/noInvisibleAdvance はめったに必要とならなくなりました。それらの主な目的は、グラフの一部の処理を省略することにあるのです。これらを使用して onEnterFrame またはアニメートされたオブジェクトを無効にすることは依然として可能ですが、これは、他の方法 (onEnterFrame ハンドラそのものを削除するなど) によっても可能です。

onClipEvent と on イベント

onClipEvent() と on() イベントの使用を避けます。その代わりに、onEnterFrame や onPress を使用するようにします。そうすることには、次のように理由がいくつかあります。

  • 関数スタイルのイベントハンドラは、実行時にインストール可能であり、削除可能であるため。
  • 関数内部のバイトコードは、旧式の onClipEvent および on ハンドラ内部にある場合よりも最適化が適切に行われるため。主な最適化は、this および _global、引数、super などをプレキャッシュし、256 の内部レジスタをローカル変数のために使用することにあります。この最適化は、関数にのみ機能します。

問題が生じるのは、onLoad 関数スタイルのハンドラを、最初のフレームが実行される前にインストールする必要がある場合だけです。そのような場合は、次のようにして、ドキュメント化されていないイベントハンドラである onClipEvent(construct) を使用して、onEnterFrame をインストールすることができます。

onClipEvent(construct) {
  this.onLoad = function() {
    //function body
  }
}

あるいは、onClipEvent(load) を使用して、通常の関数をそこから呼び出します。ただし、この方法のほうが劣ります。余分な関数コールのために余計なオーバーヘッドが生じるためです。

onEnterFrame

onEnterFrame イベントハンドラの使用を最小限に抑えます。あるいは、最低でも、常に実行するのではなく、必要な場合にインストールおよび削除するようにします。onEnterFrame ハンドラを多用すると、パフォーマンスが顕著に低下する場合があります。代替として、setInterval および setTimeout 関数の使用を検討してください。setInterval を使用する場合は次のことに留意してください。

  • このハンドラが不要になったら、必ず、clearInterval を呼び出します。
  • setInterval および setTimeout ハンドラは、onEnterFrame よりも頻繁に実行するならば、onEnterFrame よりも低速になる場合があります。これを避けるためには、時間間隔のために控え目な値を使用します。

onEnterFrame ハンドラを削除するには、delete 演算子を使用します。

delete this.onEnterFrame;
delete mc.onEnterFrame;

onEnterFrame に null または未定義をアサインしてはいけません (例 : this.onEnterFrame = null)。その理由は、この操作によって、onEnterFrame ハンドラが完全に削除されることがないためです。onEnterFrame という名前が依然として存在するため、GFx がなおもこのハンドラを削除しようとします。

Var キーワード

可能な場合は必ず、var キーワードを使用します。var キーワードを関数内部で使用することは特に重要です。その理由は、ActionScript のコンパイラがローカル変数へのアクセスを最適化する際に、変数をハッシュテーブルに入れて名前でアクセスするのではなく、インデックスによる直接的なアクセスをともなって内部レジスタを使用するためです。var キーワードを使用することによって、AS の関数実行速度が倍になります。

以下は、最適化されていないコードです。

var i = 1000;
countIt = function() {
  num = 0;
  for(j=0; j<i; j++) {
    j++;
    num += Math.random();
  }
  displayNumber.text = num;
}

以下は、最適化されたコードです。

var i = 1000;
countIt = function() {
  var num = 0;
  var ii = i;
  for(var j=0; j<ii; j++) {
    j++;
    num += Math.random();
  }
  displayNumber.text = num;
}

プレキャッシュ

ローカル変数 (var キーワードをともなった) において、頻繁にアクセスする読み取り専用のオブジェクト メンバーをプレキャッシュします。

以下は、最適化されていないコードです。

function foo(var obj:Object) {
  for (var i = 0; i < obj.size; i++) {
    obj.value[i] = obj.num1 * obj.num2;
  }
}

以下は、最適化されたコードです。

function foo(var obj:Object) {
  var sz = obj.size;
  var n1 = obj.num1;
  var n2 = obj.num1;
  for (var i = 0; i < sz; i++) {
    obj.value[i] = n1*n2;
  }
}

プレキャッシュは、他のシナリオについても効果的に使用することができます。他の例としては、次のようなものがあります。

var floor = Math.floor
var ceil = Math.ceil
num = floor(x) ? ceil(y);
var keyDown = Key.isDown;
var keyLeft = Key.LEFT;
if (keyDown(keyLeft)) {
  // do something;
}

長いパスをプレキャッシュします。

次のような長いパスを繰り返し使用しないようにします。

mc.ch1.hc3.djf3.jd9._x = 233;
mc.ch1.hc3.djf3._x = 455;

ローカル変数においてファイルパスの一部をプレキャッシュします。

var djf3 = mc.ch1.hc3.djf3;
djf3._x = 455;
var jd9 = djf3.jd9;
jd9._x = 223;

複雑な式

次のような、複雑で C スタイルの式を避けます。

this[_global.mynames[queue]][_global.slots[i]].gosplash.text = _global.MyStrings[queue];

この式を小さな部分に分割して、中間的なデータをローカル変数に格納します。

var _splqueue = this[_global.mynames[queue]];
var _splstring = _global.MyStrings[queue];
var slot_i = _global.slots[i];
_splqueue[slot_i].gosplash.text = _splstring;

このことは、次のループのように分割された部分に複数の参照が存在する場合は、特に重要です。

for(i=0; i<3; i++) {
  this[_global.mynames[queue]][_global.slots[i]].gosplash.text = _global.MyStrings[queue];
  this[_global.mynames[queue]][_global.slots[i]].gosplash2.text = _global.MyStrings[queue];
   this[_global.mynames[queue]][_global.slots[i]].gosplash2.textColor = 0x000000;
}

次は、この前のループを改良したバージョンです。

var _splqueue = this[_global.mynames[queue]];
var _splstring = _global.MyStrings[queue];
for (var i=0; i<3; i++) {
  var slot_i = _global.slots[i];
  _splqueue[slot_i].gosplash.text = _splstring;
  _splqueue[slot_i].gosplash2.text = splstring;
  _splqueue[slot_i].gosplash2.textColor = 0x000000;
}

上記のコードはさらに最適化することができます。可能な場合には、配列の同一要素への複数の参照を取り除きます。ローカル変数において、解決されたオブジェクトをプレキャッシュします。

var _splqueue = this[_global.mynames[queue]];
var _splstring = _global.MyStrings[queue];
for (var i=0; i<3; i++) {
  var slot_i = _global.slots[i];
  var elem = _splqueue[slot_i];
  elem.gosplash.text = _splstring;
  var gspl2 = elem.gosplash2;
  gspl2.text = splstring;
  gspl2.textColor = 0x000000;
}