cellfusion先生のサンプルを写経しながら1日でProgression(タイムラインスタイル)を学ぶ

※このエントリは、Progression初心者向けです。コマンドって何じゃ? addCommandの中には何を書けばいいんじゃ? というレベルの僕がだいたいの概念を理解したいがために書きなぐるものです。

こちらのサンプルをガン見しながらProgressionのお作法というのを学ぼうと思い立ちました。サンプル自体の紹介記事(cellfusion先生)はこちら。

本当なら「Progressionを使わないとこんな感じになる」という自作サンプルを提示した上で見ていくべきなんですが、とにかくProgressionを把握するためにそのまーんま追っていきます。
さらに言うと、こういうのは頭の中でやってればいいことです。アホ丸出しなので周回遅れの気がします。

構成

・最初のページを表示(overview/news/showcase/supportを読み込むボタンがあるページ)
・画面左にナビゲーションを表示(固定)

構成から考える処理の流れ

・最初のページを表示
・グローバルナビはそれぞれのページのコンテンツリンクと、ブログなどのアンカーリンク
・ナビのボタンを押すとそれぞれのページを表示
・newsではindex.swfと同階層のnews.xmlを読み込んで表示
・showcaseではindex.swfと同階層のshowcase.xmlを読み込んで表示

スクリプト概要

0:準備
0-1:フレームスクリプトで関数を設定。
function updateProgress(percent:Number)。progressViewの読み込み数字を変更する部分。
0-2:フレームスクリプトにロード完了リスナを設定。
loaderInfo.addEventListener( Event.COMPLETE, _complete );

1:ロード完了後の処理(初期設定):以下はすべて_completeの内容。
1-1:progressionインスタンスを作成
prog = new Progression( “index”, stage );
1-2:画面に常に表示するものを追加
bg、ヘッダ、グローバルナビ
1-3:ライブラリから各ページのインスタンスを作成(ページとはIOアニメーションがついているもの)。ページの中身が動的な場合は、それぞれのページを使うシーンで設定。

2:最初のシーンの設定。
prog.rootについての設定。bg、ヘッダ、グローバルナビをコンテナに追加と、indexPageをコンテナに追加。その後のシーンは全部SceneObjectを作ってから画面に追加しているが、indexPageはSceneObjectを作らないでよい。っていうのは、prog.rootというSceneObjectに直接追加しているため。
2-1:prog.root.onLoad
2-2:prog.root.onInit
2-3:prog.root.onGoto
2-4:prog.root.onUnload

3:overviewシーンの設定
3-1:overviewシーンを作成
var overview:SceneObject = new SceneObject(“overview”);
3-2:overview.onInit
3-3:overview.onGoto
3-4:prog.root.addScene(overview);
最低限のシーンなら、シーンオブジェクトを作ってonInitとonGotoを設定した上でprog.rootに追加すればいいということがわかる。

4:newsシーンを作成
4-1:var news:SceneObject = new SceneObject(“news”);
4-2:news.onLoad
4-3:news.onInit
4-4:news.onGoto
4-5:news.onUnload
4-6:prog.root.addScene(news);
シーンについて詳細に作るなら、onLoad/onInit/onGoto/onUnloadを設定すればいいということがわかる。

5:showcaseシーンを作成
5-1:var showcase:SceneObject = new SceneObject(“showcase”);
5-2:showcase.onLoad
5-3:showcase.onInit
5-4:showcase.onGoto
5-5:showcase.onUnload
5-6:prog.root.addScene(showcase);

6:support シーンを作成
6-1:var support:SceneObject = new SceneObject(“support”);
6-2:support.onInit
6-3:support.onGoto
6-4:prog.root.addScene(support);

7:最初のシーンに移動
7-1:prog.goto( prog.firstSceneId );

前提知識

・onLoad→onInit、でシーンが読み込まれ、onGoto→onUnloadでシーンが終わる。
Gotoしてから子シーンをLoad、Initするという遷移。子や孫、親へのシーン遷移は別途覚えるのがいいんだけど、なんとなく分かった気になる。
・addCommand内は一行書くごとに;ではなくて,を使う。これは文じゃないから。OK。

よく使うコマンド

このサンプルには出てこないのもありますが、前提として最低限、こんなコマンドがあるんだねということを覚えておかないと。
・Trace
・Wait
・AddChild
・AddChildAt
・RemoveChild
・RemoveAllChildren
・DoTweener
・LoadURL
・LoadChild

よく分からないところ

・XMLを読み込んだデータはキャストとして追加するのかシーンなのか、そのやり方
・すべてはコマンドで記述しないといけないのか。→addCommandの中に普通にfunction()とかも書けるので、今までのスタイルでも安心。

難しげなことをやってる部分を追っていく

ここでは2つ。newsのXMLを読み込んで表示する部分と、showcaseのXMLを読み込んで表示する部分です。

newsのXMLを読み込んで外部swfを表示

news.onLoad = function():void {
// progressView を初期化する
progressView.bar.scaleX = 0;
progressView.state.text = "0%";
this.addCommand(
new AddChildAt(prog.container, progressView, 1000),
new LoadURL(new URLRequest("news.xml")),
function():void {
var newsXml:XML = XML(this.previous.data);
var l:uint = newsXml.news.length();//XML内の>news<タグの数
loadedNum = 0;
totalNum = l;
for (var i:uint = 0; i < l; i++) {
var s:XML = newsXml.news[i];
this.parent.insertCommand( //XML内のsrcアトリビュート
new LoadObject(new CastLoader(), new URLRequest(s.@src), {
loader:{
y:i*72
},
onProgress:function():void { //読み込み中のバー数字
var max:Number = 1 / totalNum;
var percent:Number = this.bytesLoaded / this.bytesTotal * max + loadedNum * max;
updateProgress(percent);
},
onCastLoadComplete:function():void {
loadedNum++;
this.parent.insertCommand(new AddChild(newsPage.content, this.loader));
}
})//end LoadObject
);//end insertCommand
}//end for
},//end function
new RemoveChild(prog.container, progressView)
);
};

プログレスバーを表示しないでいいや、実際の処理の流れだけを知りたいわ、と思うと、こうなります。

news.onLoad = function():void {
this.addCommand(
new LoadURL(new URLRequest("news.xml")), // XML を読み込む
function():void {
var newsXml:XML = XML(this.previous.data);
var l:uint = newsXml.news.length();//XML内の>news<タグの数
loadedNum = 0;
totalNum = l;
for (var i:uint = 0; i < l; i++) {
var s:XML = newsXml.news[i];
this.parent.insertCommand( //XML内のsrcアトリビュート
new LoadObject(new CastLoader(), new URLRequest(s.@src), {
loader:{
y:i*72
},
onCastLoadComplete:function():void {
loadedNum++;
this.parent.insertCommand(new AddChild(newsPage.content, this.loader));
}
})//end LoadObject
);//end insertCommand
}//end for文
}//end function
);//end addCommand
};

まず、var newsXml:XML = XML(this.previous.data);ですが、this.previous.dataはthis.latestDataでも同じ結果になりました。progression用語ですね。で、読み込みの処理の流れはだいたい見た感じで分かるとして、気になるのはここで2回も出ているthis.parent.insertCommandです。AddCommandとinsertCommandはどう違うのか?と、思う前に、コマンドってそもそもどういう流れで実行されるの? という疑問にはnorthprint先生のこちらのエントリが詳しいです。
内容は端折りますが、addCommandするとコマンドを実行するキューみたいなのができて、それが上から順番に処理されていく、ということらしいです。

では、ここでいうthis.parentとは何か? です。for文の最初に出てくるやつはloaderオブジェクトを作って読み込むっていう処理なので、予想としては「this.addCommand」、onCastLoadComplete内のinsertCommandはAddChildしているので、予想としては「LoadObject」ではないかと。
結果。
最初の:command
次の:なんと最初のと同じcommand。どうやらaddCommandらしい。
つまり、addCommand内にinsertCommandしているということらしい。

thisとかparentとかスコープがどこなのかを理解するのがポイントだと感じました。

showcaseのXMLを読み込んで外部swfを表示

ノリとしてはnewsのそれと似ているんですが、もっと複雑です。プログレスバーの部分を除いたコードがこちら(※プログレスバーはXMLのロードとdetailSWFの読み込みで2回addChildされています)。

showcase.onLoad = function():void {
this.addCommand(
new LoadURL(new URLRequest("showcase.xml")), // XML を読み込む
function():void {
// 読み込んだ XML を XML として変数に入れる
var showcaseXml:XML = XML(this.previous.data);
// 表示する showcase の数を確認
var l:uint = showcaseXml.showcase.length();
// showcase ごとの swf を読み込むのでパラメーターを初期化
loadedNum = 0;
totalNum = l;
// XML を元にシーンを作成したり、 swf を読み込む
for (var i:uint = 0; i < l; i++) {
// XMLの>showcase <の情報を抜き出し
var s:XML = showcaseXml.showcase[i];
// 詳細ページのシーンを作成(XMLのidアトリビュートからシーンオブジェクトを作成。
var detail:SceneObject = new SceneObject(s.@id);
detail.onLoad = function():void {
// 読み込むファイル数などのパラメータを初期化
loadedNum = 0;
totalNum = 1;
this.addCommand(
new LoadObject(new CastLoader(), new URLRequest(s.@detail), {
loader:{
id:"detailContent" // 読み込んだオブジェクトを判別するための ID を指定
},
onCastLoadComplete:function():void {
// 読み込みの完了したファイル数を 1 増やす
loadedNum++;
// 読み込んだ swf を表示するために、AddChild コマンドをこのコマンドの後に実行するように insertCommand を使って割り込みさせる
this.parent.insertCommand(new AddChild(showcaseDetailPage.content, this.loader));
}
}),//LoadObjectここまで
);//this.addCommand
};//detail.onLoad
detail.onInit = function():void {
this.addCommand(
new AddChildAt(prog.container, showcaseDetailPage, 900) // 詳細ページを追加
);
};
detail.onGoto = function():void {
this.addCommand(
new RemoveChild(prog.container, showcaseDetailPage) // 詳細ページを削除
);
};
detail.onUnload = function():void {
this.addCommand(
new RemoveChild(showcaseDetailPage.content, getInstanceById("detailContent")) // LoadObject にて読み込んだオブジェクトを消去
);
};
// シーンを追加する(指定した番号に既にある場合は上書きされる)
showcase.addSceneAtAbove(detail, i);
// コマンドを追加する
this.parent.insertCommand(
new LoadObject(new CastLoader(), new URLRequest(s.@src), {
loader:{
y:i*144
},
onCastLoadComplete:function():void {
loadedNum++;
this.parent.insertCommand(new AddChild(showcasePage.content, this.loader));
}
})//end LoadObject
);//end insertCommand
}//end for文
}//end function
);//end addCommand
};

ややこしやなのは、detailの部分。シーンオブジェクトなので、それに対してonLoadが設定できるのがshowcaseのonLoadとゴッチャになって分かりにくかったです。showcaseのonLoadの中でdetailのonLoad/onInit/onGoto/onUnloadを設定しています。
detailはonGotoで読み込んだ内容をRemoveChildしないといけないので、ちゃんと設定しましょうということです。

まとめ

グローバルナビだけのサンプルだと、シーンの追加や遷移はできるけど外部読み込みはどうすりゃいいのかわかんね、っていうことになりそうですが、このサンプルは一通り「よくあること」を網羅してるので、これを写経するだけでも分かった気になります。

クラスの書き方は知らないけど、これでProgを使う気になれる、というところまでモチベーションが上がりました。