[AS3] シーケンス遷移(ページ遷移)を自作する1

2010/02/3

こんにちは。きんくまです。
東京や千葉では昨夜、雪が降りました。
すぐに溶けてしまったのですが、子供は物心ついて初めて見た雪なので喜んでいました。
2年前にも見たのですが、あのときは1歳ちょっとだったから。

さて、今回はシーケンス遷移を自作してみようと思います。
シーケンス遷移は、ゲーム業界で使われている用語のようです。
普通にいうとページ遷移です。
なんでゲーム業界のことなんか出てくるんだ?といいますと、今回は下の本を参考に作ったからなのです。

>> ゲームプログラマになる前に覚えておきたい技術

これはセガの中の人が書いた本です。C++の本なのですが、考え方は別の言語でも
使えるということでAS3で拝借しました。
この本の中でシーケンス遷移は2章割かれていまして、簡単なのと難しいのがあります。
今回は難しい方をやってみようと思います。
もっとも、難しいといってもオブジェクト指向やデザインパターンなどを熟知している人からしたらすごく簡単だと思います。
私には難しかったです。

さて、今回のデモ

>> ファイル一式

 
シーケンスやページという呼び方でもいいんですが、今回はProgressionに習って
シーンという呼び方にします。
シーン構成はこんな感じ。

Main(シーンなしこれを基底とする)
 ┣ タイトルシーン
 ┣ オプションシーン
 ┣ ゲームシーン
    ┣ ポップアップシーン

Main直下に、タイトル・オプション・ゲームが並んでいて、
ゲームにポップアップがぶら下がっている形。
ゲームが表示されている上にポップアップが出ていることに注目してください。

各シーンはSceneBaseを継承しています。

package test.base
{
	import flash.display.MovieClip;
	import flash.events.Event;
	/**
	 * ...
	 * @author KinkumaDesign
	 */
	public class SceneBase extends MovieClip
	{
		protected var _nextScene:SceneBase;
		
		public function getNextScene():SceneBase
		{
			return _nextScene;
		}
		
		public function SceneBase() 
		{
			
		}
		
		public function init():void
		{
			
		}
		
		public function startScene():void
		{
			
		}
		
		public function stopScene():void
		{
			
		}
		
		protected function dispatchCompleteEvent():void
		{
			dispatchEvent(new Event(Event.COMPLETE));
		}
	}

}

ポップアップだけがSceneBaseを継承したGameSceneBaseをさらに継承しています。

package test.base
{
	/**
	 * ...
	 * @author KinkumaDesign
	 */
	public class GameSceneBase extends SceneBase
	{
		
		public function GameSceneBase() 
		{
			
		}
		
	}

}

それで、親と子の同階層間移動の基本方針は以下のようになります。

1)一番最新の子(画面手前)が自分の次のシーンを決めてnewする
2)親は子供からEvent.Completeを受け取ったあと、子供から次のシーンの情報をもらう。
3)親は現在の子供をstopSceneして、次のシーンをinitし、startSceneする。
4)親は次のシーンにaddEventLitener(Event.Complete)して待機
1)に戻る

ここで、全てのシーンはSceneBaseを継承しているので、親は子供が言ってきた次のシーンが何であるかわからないまま進めることができます。
Mainの初期化のときだけ最初のシーンを決めますが、あとは子供の言うとおり「はいはい、次はそのシーンね」みたいな感じですすめられます。

さきほどのセガの参考書の簡単な方の章では、別のやり方をしています。
1)子供の中でnewするのではなくて、子供が次のシーンの番号だけを保持しておく
2)親は子供の番号を見て次のシーンを分岐、親がnewする

ここで、今回のやり方との違いは親と子供の仕事の分担です。
親がnewする場合は、次に行くシーンの数だけif文が親の中に並ぶことになります。
それだと、シーンの数が増えた場合に可読性が下がるので、子供の方で分岐を分割してあげようというのが今回の方針です。
たとえば次の行き先が全部で10あったとしても、一人の子供が示す次の行き先は2~3である場合があるからです。

ちなみに、本の方ではC++を使っているので、newした参照ではなくポインタを渡していました。newした参照とポインタって同じもんだと思ってるんですけど、ちょっと違うんでしょうかね??このあたりよくわかりません。

Mainのソース

package  test
{
	import flash.display.MovieClip;
	import flash.events.Event;
	import test.base.SceneBase;
	/**
	 * ...
	 * @author KinkumaDesign
	 */
	public class Main extends MovieClip
	{
		private var _childScene:SceneBase;
		
		public function Main() 
		{
			init();
		}
		
		private function init():void
		{
			_childScene = new TopScene();
			_childScene.init();
			addChild(_childScene);
			_childScene.startScene();
			_childScene.addEventListener(Event.COMPLETE, childSceneCompleteHD);
		}
		
		private function childSceneCompleteHD(e:Event):void
		{
			_childScene.removeEventListener(Event.COMPLETE, childSceneCompleteHD);
			var tempScene:SceneBase = _childScene.getNextScene(); //一時保存
			
			//終了処理
			_childScene.stopScene();
			removeChild(_childScene);
			_childScene = null;
			
			
			//新シーン処理
			_childScene = tempScene;
			_childScene.init();
			addChild(_childScene);
			_childScene.startScene();
			_childScene.addEventListener(Event.COMPLETE, childSceneCompleteHD);
		}
	}

}

タイトル画面 TopScene

package test 
{
	import flash.display.MovieClip;
	import flash.events.MouseEvent;
	import test.base.SceneBase;
	/**
	 * ...
	 * @author KinkumaDesign
	 */
	public class TopScene extends SceneBase
	{
		public var startBtn:MovieClip;
		public var optionBtn:MovieClip
		
		public function TopScene() 
		{
			
		}
		
		override public function init():void 
		{
			super.init();
		}
		
		override public function startScene():void 
		{
			startBtn.addEventListener(MouseEvent.CLICK, startClickHD);
			optionBtn.addEventListener(MouseEvent.CLICK, optionClickHD);
			super.startScene();
		}
		
		private function optionClickHD(e:MouseEvent):void 
		{
			_nextScene = new OptionScene();
			dispatchCompleteEvent();
		}
		
		private function startClickHD(e:MouseEvent):void 
		{
			_nextScene = new GameScene();
			dispatchCompleteEvent();
		}
		
		override public function stopScene():void 
		{
			startBtn.removeEventListener(MouseEvent.CLICK, startClickHD);
			optionBtn.removeEventListener(MouseEvent.CLICK, optionClickHD);
			super.stopScene();
		}
	}

}

オプションシーンはタイトルシーンとほぼ同じなので省略。
親と子供を両方もち、処理が複雑になるゲームシーンについて。

今回、ゲームからポップアップを開いたあとに、ゲームシーンから見て、ポップアップをただ閉じるだけという場合と、自分を飛び越えて自分の兄弟であるタイトル画面へ行くという仕様にしました。

ゲームシーンの子シーンは全てGameSceneBaseを継承するようにします。
そうしたときに、
if(次のシーン is GameSceneBase)
と型判定をすると自分にまだ関係があるかどうかわかります。
もし自分にもう関係ないや、ということであれば自分の親、つまりこの場合はMainに判定してもらおう。という仕組みです。
その場合、自分にはもう関係ないのでゲームシーンがイベントを発行して親にひろってもらいます。

GameScene

package test 
{
	import flash.display.MovieClip;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import test.base.GameSceneBase;
	import test.base.SceneBase;
	/**
	 * ...
	 * @author KinkumaDesign
	 */
	public class GameScene extends SceneBase
	{
		public var popupBtn:MovieClip;
		public var titleBtn:MovieClip;
		private var _childScene:SceneBase;
		
		public function GameScene() 
		{
			
		}
		
		override public function init():void 
		{
			super.init();
		}
		
		override public function startScene():void 
		{
			popupBtn.addEventListener(MouseEvent.CLICK, popupClickHD);
			titleBtn.addEventListener(MouseEvent.CLICK, titleClickHD);
			super.startScene();
		}
		
		private function popupClickHD(e:MouseEvent):void 
		{
			_childScene = new PopupScene();
			_childScene.init();
			addChild(_childScene);
			_childScene.startScene();
			_childScene.addEventListener(Event.COMPLETE, childSceneCompleteHD);
		}
		
		private function childSceneCompleteHD(e:Event):void 
		{
			_childScene.removeEventListener(Event.COMPLETE, childSceneCompleteHD);
			_childScene.stopScene();
			removeChild(_childScene);
			
			_nextScene = _childScene.getNextScene();
			
			//何もしない
			if (_nextScene == null) {
			
			//GameSceneの子シーンになれるんだったら
			}else if (_nextScene is GameSceneBase) {
				/*
				 * 子シーンを追加してchildSceneCompleteHDを登録する処理
				 * 今回は別の子シーンがないので何もしない
				 */
				
			//子シーンでない場合は親に通達する
			}else {
				dispatchCompleteEvent();
			}
		}
		
		private function titleClickHD(e:MouseEvent):void 
		{
			_nextScene = new TopScene();
			dispatchCompleteEvent();
		}
		
		override public function stopScene():void 
		{
			popupBtn.removeEventListener(MouseEvent.CLICK, popupClickHD);
			titleBtn.removeEventListener(MouseEvent.CLICK, titleClickHD);
			super.stopScene();
		}
	}

}
package test 
{
	import flash.display.MovieClip;
	import flash.events.MouseEvent;
	import test.base.GameSceneBase;
	/**
	 * ...
	 * @author KinkumaDesign
	 */
	public class PopupScene extends GameSceneBase
	{
		public var titleBtn:MovieClip;
		public var closeBtn:MovieClip;
		
		public function PopupScene() 
		{
			
		}
		
		override public function init():void 
		{
			super.init();
		}
		
		override public function startScene():void 
		{
			closeBtn.addEventListener(MouseEvent.CLICK, closeClickHD);
			titleBtn.addEventListener(MouseEvent.CLICK, titleClickHD);
			super.startScene();
		}
		
		private function closeClickHD(e:MouseEvent):void 
		{
			_nextScene = null; //画面を閉じたあと兄弟シーンにいくわけでないのでnull
			dispatchCompleteEvent();
		}
		
		private function titleClickHD(e:MouseEvent):void 
		{
			_nextScene = new TopScene();
			dispatchCompleteEvent();
		}
		
		override public function stopScene():void 
		{
			closeBtn.removeEventListener(MouseEvent.CLICK, closeClickHD);
			titleBtn.removeEventListener(MouseEvent.CLICK, titleClickHD);
			super.stopScene();
		}
	}

}

SceneBaseにinit, startScene, stopSceneがありますが、本の方ではそういう風な関数はまったくありません。あと本は、こういったイベント駆動の方式ではなくて、毎フレーム次のシーンを返してreturn nextScene;みたいにして、親の方で判定する方式です。
なので、今回のはAS3向けにアレンジしてますので、ちゃんとしたやつが見たい方は本の方を見てください。

次回はこれに非同期処理を加えたいと思います。
非同期処理はいろいろあるのですが、前から愛用しているNextを使ってみようと思っています。

LINEで送る
Pocket

自作iPhoneアプリ 好評発売中!
フォルメモ - シンプルなフォルダつきメモ帳
ジッピー電卓 - 消費税や割引もサクサク計算!

ページトップへ戻る