アクションステージのデータをGAEでセーブ&ロード

状況

アクションステージのセットデータをGAEでセーブ&ロードする見当がついたところまで。

解説

まず、セーブしようとしているデータは以下のような形式をとっている。

public static const STAGE_PARAM:Array = [
	{//Limit Wall Left
		lx:RANGE_LX-10,
		rx:RANGE_LX,
		uy:RANGE_UY,
		dy:RANGE_DY,
		density:0.0,//fixed
		category_bits:0x0004,

		name:"Block"
	},
	{//Limit Wall Right
		lx:RANGE_RX,
		rx:RANGE_RX+10,
		uy:RANGE_UY,
		dy:RANGE_DY,
		density:0.0,//fixed
		category_bits:0x0004,

		name:"Block"
	},
];


一つのオブジェクトを一つの連想配列で表し、それをリストでまとめたものが一つのステージになる。


今回は、一つのオブジェクトを一つのデータとして保存する。もしかしたら、一つのステージを一つのデータにした方が良いかもしれないが、今のところはこの方法でいく。GAEで保存するために、以下の形式で表現できるように変換する。

class SetData(db.Model):
	#一つのセットデータが一つにまとめられる
	stage_id = db.StringProperty()#ステージ識別のためのID(未使用)
	object = db.StringListProperty()#一つのオブジェクトを文字列で表し、それをリストで持つ

つまり、AS3で連想配列になっているものを、GAEで文字列のリストと相互に変換できるようにすれば、セーブ&ロードが可能になる。基本的には、連想配列のkeyとvalを文字列にして、それをリストに入れれば良い。そのためには、「連想配列のvalを、"本来の型"と"文字列"で相互に変換できるようにする」「連想配列とリストの相互変換」の二つの処理が必要になる。


文字列のみの連想配列をAS3とGAEでやりとりすることにする。なので、AS3側では「"本来の型"と"文字列"の相互変換」を、GAE側では「"文字列のみの連想配列"と"リスト"の相互変換」を行う。


まず、「"本来の型"と"文字列"の相互変換」の方だが、あらかじめ以下のように「Number型のパラメータは何か」を用意しておいて、相互変換を可能にしておく。

static private const PARAM_NUMBER:Array = [
	"lx",
	"rx",
	"uy",
	"dy",
];

これを使って、「"本来の型"→"文字列"」は以下のように行う。

#data = STAGE_PARAM
for each (var obj:Object in data){
	for each (var param:String in PARAM_NUMBER){
		obj[param] = ""+obj[param];//Number -> String
	}
}

逆に、「"文字列"→"本来の型"」は以下のように行う。

#data : GAEから返ってきたステージデータ
for each (var obj:Object in data){
	for each (var param:String in PARAM_NUMBER){
		obj[param] = Number(obj[param]);//String -> Number
	}
}

「"文字列のみの連想配列"と"リスト"の相互変換」の方は、連想配列を「key1, val1, key2, val2, ...」というリストで表現することで、相互変換を行う。


「"文字列のみの連想配列"→"リスト"」は以下のように行う。「"文字列のみの連想配列"のリスト:data」「"文字列のみの連想配列":obj」「"リスト":obj_data」となっている。

#連想配列のリストがdataとして入ってくる
for obj in data:
	#連想配列(一つのオブジェクトのパラメータ)をobjとして抜き出して処理
	obj_data = []#連想配列を、リストに変換して保存することにする
	for k, v in obj.iteritems():
		#keyとvalが交互に入ることになる
		obj_data.append(k)
		obj_data.append(v)
	#格納するデータを作成
	setdata = SetData(stage_id="stage", object=obj_data)
	#保存
	setdata.put()


「"リスト"→"文字列のみの連想配列"」は以下のように行う。「"リスト":setdata」「"文字列のみの連想配列":param」「"文字列のみの連想配列"のリスト:res」となっている(上との対応がなってないので、あとで書き直そう)。

#セットデータの配列
res = []
for setdata in q:
	phase = 0#keyを処理中か、valを処理中か
	param_key = ''#keyの値を記憶しておく
	param_val = ''#なくてもいいけど、覚えておく
	param = {}#オブジェクトのパラメータを、文字列のリストから連想配列にしてこれに入れる
	for key_val in setdata.object:
		#keyとvalが交互に入っている
		if phase == 0:#key処理中
			param_key = key_val
		else:#val処理中
			param_val = key_val
			param[param_key] = param_val#「key->val」を追加
		phase = 1 - phase#key <-> val:処理対象のスイッチ
	#セットデータが一つ求まったので追加
	res.append(param)
return res


あとは基本的に今までと同じ。AS3のファイル名を変更したので、app.yamlも変更したり、アクセスするURLがhttp://localhost:8080/Main.swfになったくらい。

コード

//Main.as

package{
	import flash.display.Sprite;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.net.NetConnection;
	import flash.net.Responder;

	public class Main extends Sprite {

		private var netConnection:NetConnection;
		private var responder_save:Responder;
		private var responder_load:Responder;
		private var output:TextField;

		public function Main() {
			output = new TextField();
			output.autoSize = TextFieldAutoSize.LEFT;
			addChild(output);

			netConnection = new NetConnection();
			netConnection.connect("http://localhost:8080/");

			responder_save = new Responder(saveOnComplete, saveOnFail);
			responder_load = new Responder(loadOnComplete, loadOnFail);

			//save test
			var data:Array = [
				{
					name:"first",
					x:0.0,
					y:0.0
				},
				{
					name:"second",
					x:10.0,
					y:20.0
				},
			];
			save(data);

			//load test
			load();
		}

		//
		static private const PARAM_NUMBER:Array = [
			"x",
			"y",
		];

		//=Save=

		//セーブ本体
		private function save(data:Array):void{
			//Python側の save を呼び出す。

			//Convert
			for each (var obj:Object in data){
				for each (var param:String in PARAM_NUMBER){
					obj[param] = ""+obj[param];//Number -> String
				}
			}

			//Save
			netConnection.call("save", responder_save, data);//save(data)     
		}

		//セーブ成功時
		private function saveOnComplete():void {
		}

		//セーブ失敗時
		private function saveOnFail(results:*):void {
			//原因を表示
			for each (var thisResult:String in results){
				output.appendText(thisResult);
			}
		}

		//=Load=

		//ロード本体
		private function load():void{
			//Python側の load を呼び出す。
			netConnection.call("load", responder_load);//load()
		}

		//ロード成功時
		private function loadOnComplete(data:Array):void {
			//Convert
			for each (var obj:Object in data){
				for each (var param:String in PARAM_NUMBER){
					obj[param] = Number(obj[param]);//String -> Number
				}
			}

			//Output
			for each (var val:Object in data){
				output.appendText("name:" + val.name + "\n");
				output.appendText("x:" + val.x + "\n");
				output.appendText("y:" + val.y + "\n");
			}
		}

		//ロード失敗時
		private function loadOnFail(results:*):void {
			//原因を表示
			for each (var thisResult:String in results){
				output.appendText(thisResult);
			}
		}
	}
}
#main.py

import os
import wsgiref.handlers
from pyamf.remoting.gateway.wsgi import WSGIGateway
from pyamf import ASObject

from google.appengine.api import users
from google.appengine.ext import db,webapp
from google.appengine.ext.webapp import template


#格納するデータ(db.Modelを継承する必要がある)
class SetData(db.Model):
	#一つのセットデータが一つにまとめられる
	stage_id = db.StringProperty()#ステージ識別のためのID(未使用)
	object = db.StringListProperty()#一つのオブジェクトを文字列で表し、それをリストで持つ

#Save
def save(data):
	#連想配列のリストがdataとして入ってくる
	for obj in data:
		#連想配列(一つのオブジェクトのパラメータ)をobjとして抜き出して処理
		obj_data = []#連想配列を、リストに変換して保存することにする
		for k, v in obj.iteritems():
			#keyとvalが交互に入ることになる
			obj_data.append(k)
			obj_data.append(v)
		#格納するデータを作成
		setdata = SetData(stage_id="stage", object=obj_data)
		#保存
		setdata.put()

#Load
def load():
	#全てのデータを取ってくるクエリを作成
	q = SetData.all()
	#セットデータの配列
	res = []
	for setdata in q:
		phase = 0#keyを処理中か、valを処理中か
		param_key = ''#keyの値を記憶しておく
		param_val = ''#なくてもいいけど、覚えておく
		param = {}#オブジェクトのパラメータを、文字列のリストから連想配列にしてこれに入れる
		for key_val in setdata.object:
			#keyとvalが交互に入っている
			if phase == 0:#key処理中
				param_key = key_val
			else:#val処理中
				param_val = key_val
				param[param_key] = param_val#「key->val」を追加
			phase = 1 - phase#key <-> val:処理対象のスイッチ
		#セットデータが一つ求まったので追加
 		res.append(param)
	return res

#対応付け
services = {
	'save': save,
	'load': load,
}

#Main
def main():
	application = WSGIGateway(services)
	wsgiref.handlers.CGIHandler().run(application)


if __name__ == '__main__':
	main()