LoadVarsと戦ってみた

Flashでテキストファイルを読み込む場合、
LoadVarsクラスのお世話になることが多いかと思います。
ですがこいつ、けっこう気難しいやつです(´・ω・`)


例として、以下のファイル(test.txt)を読み込む場合で考えてみます。

test=hogehoge


これはTest.as。メインのプログラムです。

class Test {
  function Test() {
    var lv = LoadVars();
    
    lv.onLoad = function(success) {
      if (success) {
        // test.txtのtest=以降を出力
        // このthisはlv(LoadVars)
        trace(this.test);
      }
    };
    lv.load("test.txt");
  }
}


そしてflaに書くプログラムです。
AS用のレイヤーの最初のキーフレームにこれを書く。

var test = new Test();


出力結果はこう。

hogehoge


ここまでは普通です。何の疑問もありません。
次にちょっとTest.asを書き換えてみます。

class Test {
  // test.txtの中身を保存するための変数
  var data;

  function Test() {
    var lv = LoadVars();
    
    lv.onLoad = function(success) {
      if (success) {
        // dataにtest.txtの中身を保存
        data = this.test;
      }
    };
    lv.load("test.txt");

    // data(test.txtの中身)を出力
    trace(data);
  }
}


実行結果です。

undefined


!!?
undefined!未定義!なんで!?
と最初はびっくりしましたが、ちゃんと理由はあります。
上には、

// dataにtest.txtの中身を保存
data = this.test;

と書きましたが、問題はここです。
dataはTestのメンバ変数ですが、
この文が書かれているのはLoadVarsの領域です。
だからここに出てくるdataは、LoadVarsのdataです。
宣言はしていないので、この瞬間に新しく作られています。
そうです。Testのdataには何も値が渡されていないのです。
納得です。


じゃあLoadVarsの中からTestのdataに値を渡すにはどうしたらいいんだろう?
と悩んでいましたが、
これについては既に解決策を見つけ出している方がいました。
参考: http://hima.chu.jp/flash/tips/whatisthis.htm
なのでありがたくその方法を使わせてもらいました。
ありがたやありがたや。
するとこうなります。

class Test {
  // test.txtの中身を保存するための変数
  var data;

  function Test() {
    var lv = LoadVars();
    // Testが持つdataへの参照をlv.dataに渡す
    // これでlv.dataからTestのdataにアクセスできる
    lv.data = this.data;
    
    lv.onLoad = function(success) {
      if (success) {
        // lv.data(つまりTestのdata)にtest.txtの中身を保存
        lv.data = this.test;
      }
    };
    lv.load("test.txt");

    // data(test.txtの中身)を出力
    trace(data);
  }
}


なるほどです。
これならたしかにうまくいきそうです。
で、どうなったか見てみると・・・

undefined


Σ(゚∀゚*)!!?
ダメじゃん。。嘘つきじゃん。。orz


全然分からなくなってしまったのですが、
ここで僕なりに1つの仮説を立ててみました。
LoadVarsのonLoadイベントハンドラは、
ファイルの解析が終わって、
LoadVarsインスタンスに変数が保存されてから呼び出されます。
だから、このタイミングはload()してすぐというわけではないので、
load()のすぐ下にあるtrace(data)の時点では、
まだdataに値は渡されていないのではないか?と。。


じゃあそれならってわけで、
ちょっとタイミングをずらしてみようと思いました。
ムービークリップを1つ作って、
そいつに以下のようなスクリプトを埋め込みます。

// クリックされたときにTestのexec()メソッドを実行
on (release) {
  _root.test.exec();
}


それでTest.asには以下を追加です。

function exec(){
  trace(data);
}


そして実行。今度はどうだ。。クリック!クリック!

undefined
undefined
undefined


(´・ω・`)・・・もうねアボガドバナナガね・・・
ああああああああああああ!!


タイミングをずらしてもダメなようです。
もうあれですか。
ファイルから読み込んだ内容は、
LoadVarsの中だけで何とかしろと。そういうことですか。


もうこれが仕様ならしょうがないかなぁと諦めていたんですが、
何とかする方法を発見しました!
これまで使ってきたdataはただの変数でしたが、
ArrayやObjectを使えばうまくいくのです!


Arrayを使ったTest.asはこんな感じです。

class Test {
  // こいつを使います
  var ary:Array;

  function Test() {
    // 初期化を忘れずに
    ary = new Array();
    var lv = new LoadVars();
    // この代入でthis.aryとlv.aryを同じものとして扱えます
    lv.ary = this.ary;    
    
    lv.onLoad = function(success) {
      if (success) {
        // aryにtest.txtの中身を入れる
	this.ary.push(this.test);
      }
    };
    lv.load("test.txt");
  }

  function exec() {
    // aryを出力
    trace(ary);
  }
}


実行結果です。

hogehoge


キタワァ*・゜゚・*:.。..。.:*・゜(n‘∀‘)η゚・*:.。. .。.:*・゜゚・*!!!!
やりました。ついにできました。
ここまで長かった・・・。
exec()を使わずに(つまりタイミングをずらさずに)、
traceしたらやっぱりundefinedだったので、
どうやら仮説も正しかったようです。


ちなみにObjectを使ったやり方は、こんな具合です。

class Test {
  // Objectはハッシュ(連想配列)として使うことができます
  var hash:Object = {data:null};

  function Test() {
    var lv = new LoadVars();
    lv.hash = this.hash;    
    
    lv.onLoad = function(success) {
      if (success) {
	this.hash.data = this.test;
      }
    };
    lv.load("test.txt");
  }

  function exec() {
    trace(hash.data);
  }
}


さて、dataではダメだったのに、
どうしてArrayやObjectではうまくいったのか・・・。
また後で悩むのも嫌なので考えてみることにしました。
dataとArrayの違い・・・違いか・・・。
何となく分かりました。
でも説明するのが難しいです。
だけどがんばって書いてみます。


Arrayというのは、dataと違い二重の参照を持っています。
Array本体への参照と、Arrayの要素への参照です。
これはObjectにも言えることです。
dataのような普通の変数は参照を1つしか持てません。
これがdataとArrayやObjectとの違いだと思います。


で、ここから今回の結論へと結び付けます。
今回のポイントになったのは、LoadVarsの中での変数の扱いです。
TestオブジェクトとLoadVarsオブジェクトの2つのオブジェクト間で、
変数の扱いが独立しているからこんなに手間取ったわけです。
オブジェクトが持つ変数というのは、
そのオブジェクトを介してしか扱うことができず、
オブジェクトごとに独立しているのでしょう。
このことは、ArrayやObjectにも言えることです。
だから、ただの変数のdataでは維持できなかった値も、
ArrayやObjectならずっと持っていることができたのだと思います。
と、これが僕なりに捻り出した仮説です。
あー疲れた(´ ▽`)


あ、そうそう。
参考サイトをよく読んでみたら、
参考サイトのプログラムでは、テキストフィールドへ値を渡しているようですね。
テキストフィールドも二重の参照を持っているので、
上の仮説で説明できると思います。


にしても、Flashはファイル読み込むだけでどんだけ苦労するんだと。。
他のプログラミング言語と同じように考えていた僕が間違いだったようです。
Flashはファイル入出力が必要なプログラムには向いていません。
そもそもそれをできるようには作られていないようです。
RTSのゲームを作るために、
マップエディタを作ろうと思っていたのですが、
それについては考えを改めようと思います。
今回、ファイル読み込みと色々格闘したわけですが、
よく考えてみると、ファイル書き込みもできないといけないわけで、
そのことも考えるとさらに面倒なことになりそうです。
だからとりあえず後回しにします。保留!
A*の実装自体はもう終わっているのに、
マップはファイルから読み込めた方がいいよな、
とか先のことを考えてたら全然進んでないよ!うわーん!ヽ(`Д´)ノ


余談ですが、
このエントリを書くときにこのテストプログラムを書いたのだけど、
Test.asの先頭のTを大文字にし忘れてハマりました。
クラスTestが見つかりませんとか言ってきてもいいだろうに、
Flashは何もエラーを返さないんですよね。
実行前にシンタックスエラーを吐けないのはもちろん分かるんですが、
実行時にも何も言わないっていうのはひどいよ・・・。
日本語の検索しやすいヘルプがあることといい、書きやすさといい、
やっぱりRubyが一番コーディングしやすい気がします。