サイトトップ

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

Optimizing Performance of ActionScript 3.0

Chapter 01 文法の基本から

□01-02 配列アクセスよりドットアクセスを使う

オブジェクトのプロパティやメソッド、あるいはタイムラインのインスタンスは、多くの場合ドットシンタックスで参照します。他方で、配列のエレメントにアクセスする配列アクセス演算子[]を使っても、これらの参照が得られます。けれど、[]演算子による配列アクセスより、ドットアクセスの方が処理は速いです。配列アクセスを使いたい場面でも、ドットアクセスで処理できないか工夫してみましょう。

【配列アクセス】
オブジェクト[プロパティ名]

工夫して↓

【ドットアクセス】
オブジェクト.プロパティ

01-02-01 ドットアクセスと配列アクセス
まずは、ドットアクセスと配列アクセスについて簡単におさらいします。すでにご存知の読者は、次項からお読みいただいて構いません。

インスタンスがもつプロパティや変数は、ドットシンタックスで読み書きすることが基本です。

インスタンス.プロパティまたは変数

たとえば、つぎのスクリプトはMovieClipインスタンスmy_mcに水平座標(DisplayObject.xプロパティ)と変数(myVariable)に値を設定したうえで、それぞれの値を[出力]パネルに表示します。

my_mc.x = 100;
my_mc.myVariable = 1;
trace(my_mc.x, my_mc.myVariable);   // 出力: 100 1

ドットアクセスの記述を配列アクセスに書替えるには、行うべきことはふたつです。

    【ドットアクセスを配列アクセスに書替えるには】
  1. 左側のドット(.)を消して配列アクセス演算子[]に替える
  2. プロパティまたは変数を文字列で指定する

つまり、配列アクセスでは、プロパティや変数をつぎのように参照します。

インスタンス[文字列のプロパティまたは変数名]

したがって、上記のドットアクセスのスクリプトは、配列アクセスでつぎのように書替えられます。

my_mc["x"] = 100;
my_mc["myVariable"] = 1;
trace(my_mc["x"], my_mc["myVariable"]); // 出力: 100 1

配列アクセスというのは、配列のエレメントにアクセスする演算子[]を使うためにこう呼ばれます。しかし、配列とは関わりがないことに注意しましょう。初心者がよく戸惑うのは、つぎの記述を配列アクセスに書替えようとするときです。

x = 100;

間違って以下のように書けば、コンパイルエラー#1084が起こります(図01-02-001)。これは、左辺が代入の対象にならないことを示すシンタックス(文法)エラーです。

["x"] = 100;

図01-02-001■誤った配列アクセスの記述でシンタックスエラーが起こる


シンタックスエラーは、左辺が代入の対象にならないことを示す。


Tips 01-02-001■コンパイルエラー#1084
コンパイルエラー#1084は、必要なものが足りないことを示すシンタックスエラーです。図01-02-001のエラーメッセージ([説明])は、きちんと日本語にするなら以下のとおりです。つまり、代入式の左辺がプロパティあるいは変数(「識別子」後掲Word 01-02-001参照)として認められないことを意味します。

識別子が代入の手前にありません。

なぜなら、左辺の「["x"]」は、文字列"x"がエレメントとして納められた配列をつくることになってしまうからです。配列に代入演算子=で値を設定することはできません。

ではどうすればよいかというと、前述「ドットアクセスを配列アクセスに書替えるには」の1「左側のドット(.)を消して」というところが鍵です。もとのステートメント「x = 100」にはドット(.)がありません。つまり、まずドット(.)のある書き方に変えなければならないのです。

this.x = 100;

したがって、正しい配列アクセスの記述はこうなります。

this["x"] = 100;

01-02-02 配列アクセスの長所と短所
ドットアクセスでなく配列アクセスを使うのは、そうする理由つまり長所があるからです。それはプロパティや変数などの識別子を、文字列で指定できることです。すると第1に、文字列を変数に納めて使えます。

var property_str:String = "x";
my_mc[property_str] = 100;

ここで、変数(property_str)の文字列を"y"にすれば垂直座標が、"rotation"に変えれば回転角が(100に)変わります。つまり、操作するプロパティを動的に切替えられるのです。

Word 01-02-001■識別子
プログラムでコントロールするインスタンス名や変数(プロパティ)、関数(メソッド)などに対してつける名前です。以下の4つの決まりがあります。

  1. すべて半角で、英数字か「_」(アンダースコア)または「$」(ドル記号)のみを用います。ただし、「$」は通常は使われません。
  2. 識別子の最初の文字に、数字を使うことはできません。
  3. 英字の大文字小文字は区別されます。
  4. ActionScriptで現在使用され、あるいは将来使われる可能性のあるキーワードとして定められている予約語を使うことはできません。

第2に、配列アクセス演算子[]に指定する文字列が動的につくれます。これは、とくにループ処理で扱うときに便利です。たとえば、つぎのスクリプトは、タイムラインに配置した名前が連番の3つのMovieClipインスタンス(my0_mc〜my2_mc)を水平に等間隔(50ピクセル)で並べます(図01-02-002)。

for (var i:uint = 0; i < 3; i++) {
  this["my" + String(i) + "_mc"].x = 50 * (i + 1);
}

図01-02-002■MovieClipインスタンスを等間隔で並べる

連番の名前をつけた3つのMovieClipインスタンスが等間隔で並べられた。

さらに、フレームアクションで配列アクセスを使うと、var宣言することなく変数に値が設定できます。配列アクセスを使わなければ、デフォルトでは変数(プロパティ)が未定義であることを示すコンパイルエラー#1120になります(図01-02-003)。

this["myVariable"] = 1;   // var宣言がなくてもエラーは起こらない

図01-02-003■var宣言していない変数にアクセスするとコンパイルエラーになる

コンパイルエラー#1120は、変数(プロパティ)が未定義つまり宣言されていないことを示す。

エラーが生じないことは、必ずしもよいとはいえません。コンパイル(SWF書出し)時に、変数の宣言が確かめられていないことを意味するからです。また、変数の宣言がないということは、当然データ型も指定されていません。つまり、型指定による最適化が働かないということです。

もっと大切なのは、仮にvar宣言でデータ型が指定された変数であっても、配列アクセスを用いると最適化された参照になりません。本節の冒頭に述べたように、最適化された参照を得るにはドットアクセスが必要なのです。したがって、最適化が求められる処理では、できるだけ配列アクセスよりドットアクセスを用いるようにしましょう。

Tips 01-02-002■強い参照
データ型の指定された変数やプロパティは、前節01-01「変数と関数にはデータ型を指定する」で述べたとおり処理が最適化され、アクセスも速くなります。これは「強い参照」と呼ばれるアクセスが働くからです。ただし、データを強い参照で扱うには、配列アクセスでなく、ドットアクセスを用いる必要があります。

    【強い参照が働くには】
  1. データ型を指定する
  2. ドットアクセスを用いる

強い参照については、マイコミジャーナル「ActionScript、Flex、そしてApollo - Flexエバンジェリストに訊くAdobe開発ツールの現在」<http://journal.mycom.co.jp/articles/2006/10/31/adobeflex/001.html>の「型指定はパフォーマンスを最適化する」をご参照ください。


01-02-03 配列アクセスの使用を最小限にする
前項に述べたとおり、処理を最適化するには、配列アクセスはできるだけ使わないように工夫します。配列アクセスがよく使われるのは、連番のインスタンスや配列を繰返し処理する場合です。たとえば、タイムラインに配置した名前が連番の3つのMovieClipインスタンス(my0_mc〜my2_mc)を水平にスクロールのアニメーションさせてみましょう(図01-02-004)。


図01-02-004■タイムラインに配置した3つのMovieClipインスタンスを水平にスクロールさせる

3つのインスタンスは水平にスクロールし、ステージの端を超えると反対側の端から表れる。

スクリプトは、インスタンスを置いたタイムラインに、フレームアクションとして書きます(スクリプト01-02-001)。アニメーションは、DisplayObject.exitFrameイベント(定数Event.EXIT_FRAME)で扱います。3つのインスタンスをどのように参照するかが課題です。

以下のように配列アクセス演算子[]に毎回文字列を連結して指定するのは、無駄が多いといえます(スクリプト01-02-001第5〜7行目)。修正があったらひとつひとつ直さなければなりませんし、タイプミスも起こりやすいです。少なくとも、文字列は最初に変数に入れるべきです。

スクリプト01-02-001【×】毎回文字列を連結して配列アクセス演算子に指定するのは無駄
    // フレームアクション
    // 名前が連番の3つのMovieClipインスタンス(my0_mc〜my2_mc)を配置
  1. var nWidth:int = stage.stageWidth;
  2. addEventListener(Event.ENTER_FRAME, xScroll);
  3. function xScroll(eventObject:Event):void {
  4.   for (var i:uint = 0; i < 3; i++) {
        // インスタンス名の文字列を毎回つくる
  5.     this["my" + String(i) + "_mc"].x += 5;
  6.     if (nWidth < this["my" + String(i) + "_mc"].x) {
  7.       this["my" + String(i) + "_mc"].x -= nWidth;
  8.     }
  9.   }
  10. }

けれど、どうせ変数に入れるなら、文字列にとどめるのでなく、以下のスクリプト01-02-002のようにインスタンスそのものを納めた方がよいでしょう(第5行目)。その後は、ドットアクセスが使えるからです。これで、最小限の最適化はできました。

スクリプト01-02-002【○】配列アクセス演算子で予めインスタンスの参照を得ればドットアクセスが使える
    // フレームアクション
    // 名前が連番の3つのMovieClipインスタンス(my0_mc〜my2_mc)を配置
  1. var nWidth:int = stage.stageWidth;
  2. addEventListener(Event.ENTER_FRAME, xScroll);
  3. function xScroll(eventObject:Event):void {
  4.   for (var i:uint = 0; i < 3; i++) {
  5.     var my_mc:MovieClip = this["my" + String(i) + "_mc"];   // 変数にインスタンスを納める
        // 後はドットアクセス
  6.     my_mc.x += 5;
  7.     if (nWidth < my_mc.x) {
  8.       my_mc.x -= nWidth;
  9.     }
  10.   }
  11. }

もっとも、ActionScript 3.0では、インスタンスを名前で取出すのは応用が利きにくく、できればインスタンスの参照を用いる方が望ましいです。そのとき考えられるのは、以下のスクリプト01-02-003のようにインスタンスの参照を配列に納めることです(第2行目)。こうすれば、インスタンス名が連番でなくても構いません。とくに、インスタンスを動的に(new演算子とコンストラクタメソッドで)つくるときは、わざわざDisplayObject.nameプロパティで名前をつけなくて済みます。

スクリプト01-02-003【○】初めからインスタンスの参照を配列に納めておく
    // フレームアクション
    // 3つのMovieClipインスタンス(my0_mc〜my2_mc)を配置
  1. var nWidth:int = stage.stageWidth;
  2. var mcs_array:Array = [my0_mc, my1_mc, my2_mc];   // インスタンスの参照を配列に納める
  3. var nLength:uint = mcs_array.length;
  4. addEventListener(Event.ENTER_FRAME, xScroll);
  5. function xScroll(eventObject:Event):void {
  6.   for (var i:uint = 0; i < nLength; i++) {
  7.     var my_mc:MovieClip = mcs_array[i];
  8.     my_mc.x += 5;
  9.     if (nWidth < my_mc.x) {
  10.       my_mc.x -= nWidth;
  11.     }
  12.   }
  13. }

Tips 01-02-003■配列アクセスと配列エレメントの取出し
配列アクセス演算子[]は、その名のとおり配列(Arrayインスタンス)エレメントにアクセスするときにも用いられます。けれど、配列アクセスで指定した文字列の名前からインスタンスを参照するより、配列エレメントを整数インデックスで取出す方が処理はずっと速いです。

[*筆者用参考]

Accessing Array elements vs dynamic variables with brackets - wonderfl build flash online


01-02-04 プロパティを配列アクセスで操作する場合 − キーコードを扱う例
プロパティをドットアクセスでなく配列アクセスで扱う場合というのは、それほど多くないと思われます。ここでは、キーコードを扱う例で考えてみましょう。

タイムラインに配置したMovieClipインスタンス(my_mc)を、キーボードの矢印キーで上下左右に動かしてみます。キーの押し下げは、InteractiveObject.keyDownイベント(定数KeyboardEvent.KEY_DOWN)で扱います。リスナー関数が引数に受取るイベントオブジェクト(KeyboardEvent.keyCodeプロパティ)から押したキーのコードがわかりますので、その整数値に応じて処理すればよいでしょう。

図01-02-005■タイムラインに配置したMovieClipインスタンスを矢印キーで上下左右に動かす

キーボードの上下左右の矢印キーで、インスタンスを1ピクセルずつ動かす。

定石といえるのは、キーコードの整数値を条件判断で切り分けることです。もっとも、キーコードはすべて値がユニークですので、配列との相性がよいです。処理したいキーコードのインデックスに、必要な情報を納めると、条件判定が要らなくなります。今回の例で必要な情報は、操作するプロパティと設定する値です。そこで、このふたつを子の配列に入れて、親配列のキーコードのインデックスに納めます。

親配列[操作したいキーコード] = [プロパティ名の文字列, 設定する値]

この考え方で矢印キーによる処理を行うのが、以下のスクリプト01-02-004です。操作したいインスタンス(my_mc)のあるタイムラインに、フレームアクションとして書きます。

配列の上下左右の矢印キーのキーコードとなるインデックスに、操作したいプロパティ(DisplayObject.xDisplayObject.y)の文字列と設定値を入れ子の配列にして、親配列(operations_array)のエレメントに納めています(スクリプト01-02-004第2〜5行目)。後は、リスナー関数でキーコードを調べて、親配列(operations_array)から操作情報のエレメントを取出し、配列アクセスでインスタンス(my_mc)のプロパティに値を設定します(第7〜14行目)。

スクリプト01-02-004【△】配列を使うと管理しやすいものの最適化はされない
    // フレームアクション
    // MovieClipインスタンスmy_mcを配置
  1. var operations_array:Array = [];
    // キーコードのインデックスにプロパティ名と値の入れ子配列をエレメントとして設定
  2. operations_array[Keyboard.LEFT] = ["x", -1];
  3. operations_array[Keyboard.RIGHT] = ["x", 1];
  4. operations_array[Keyboard.UP] = ["y", -1];
  5. operations_array[Keyboard.DOWN] = ["y", 1];
  6. stage.addEventListener(KeyboardEvent.KEY_DOWN, useArray);
  7. function useArray(eventObject:KeyboardEvent):void {
  8.   var operation_array:Array = operations_array[eventObject.keyCode];
  9.   if (operation_array) {   // 配列にエレメントがあれば
  10.     var property_str:String = operation_array[0];
  11.     var pixels:Number = operation_array[1];
  12.     my_mc[property_str] += pixels;   // インスタンスのプロパティに値を加える
  13.   }
  14. }

Tips 01-02-004■配列エレメントの条件(ブール値)評価
配列の指定したインデックスにエレメントがないと、未定義値undefinedが返ります。undefinedは条件(プール値)として評価するとfalseになります。他方、オブジェクトはtrueと評価されます。したがって、上記スクリプトのif条件は、エレメント(子配列)があればtrue、なければfalseとして扱われるのです。


Basics 01-02-001■配列を使ったキーコードとプロパティの扱い
キーコードに応じたインスタンスのプロパティを配列で処理する例について詳しくは、gihyo.jp連載「ActionScript 3.0で始めるオブジェクト指向スクリプティング」第15回「配列を使ったキーコードとプロパティの扱い」<http://gihyo.jp/dev/serial/01/as3/0015>をお読みください。

上記スクリプト01-02-004は配列アクセスでインスタンス(my_mc)のプロパティを設定していますので、処理としては最適化されていません。けれど、操作の情報を配列にまとめていますので扱いやすく、仕組みさえ変わらなければ操作対象のキーとプロパティを増やすのも簡単です。

以下のフレームアクション(スクリプト01-02-005)は、定石どおりキーコードをswitchステーメントで振分けます(第3〜16行目)。処理は配列を使う場合と比べて最適化されます。とはいえ、キー操作を1秒間に何百回も処理することは、あまり考えられません。すると、最適化で劣ることはわかったうえで、扱いやすさから配列による処理を選ぶこともあり得るでしょう。なお、配列に速さで勝るVectorオブジェクトを使う考え方は、後述03-07-02「キーボードのキーコードをVectorオブジェクトで扱う」でご紹介します。

スクリプト01-02-005【○】条件判定を使った方が最適化できる
    // フレームアクション
    // MovieClipインスタンスmy_mcを配置
  1. stage.addEventListener(KeyboardEvent.KEY_DOWN, use_switch);
  2. function use_switch(eventObject:KeyboardEvent):void {
  3.   switch (eventObject.keyCode) {
  4.     case Keyboard.LEFT :
  5.       my_mc.x -= 1;
  6.       break;
  7.     case Keyboard.RIGHT :
  8.       my_mc.x += 1;
  9.       break;
  10.     case Keyboard.UP :
  11.       my_mc.y -= 1;
  12.       break;
  13.     case Keyboard.DOWN :
  14.       my_mc.y += 1;
  15.       break;
  16.   }
  17. }

Basics 01-02-002■switchステートメントによる条件判定
switchステートメントによる条件の判定については、gihyo.jp連載「ActionScript 3.0で始めるオブジェクト指向スクリプティング」第14回「キー操作とif以外の条件判定」<http://gihyo.jp/dev/serial/01/as3/0014>の「switchステートメント」(2ページ)をお読みください。


[*筆者用参考]

Evaluating key codes with switch statement vs an array - wonderfl build flash online


作成者: 野中文雄
更新日: 2011年3月23日 スクリプト01-02-005のcaseステートメントの式の括弧()を削除。
更新日: 2011年3月16日 03-07-02「キーボードのキーコードをVectorオブジェクトで扱う」の参照を追加。
更新日: 2011年1月23日 スクリプトに連番と行番号を追加。基礎知識としてのgihyo.jpのリンクをBasicsとした。
更新日: 2011年1月15日 例のタイトルを具体化
更新日: 2010年12月28日 スクリプト冒頭に記述場所とインスタンスについてのコメントを追加
作成日: 2010年12月20日


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